@addfox/cli 0.1.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +12 -0
- package/dist/cli/args.d.ts +29 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1022 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/test.d.ts +5 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/pipeline/Pipeline.d.ts +52 -0
- package/dist/pipeline/Pipeline.d.ts.map +1 -0
- package/dist/pipeline/frameworkPlugins.d.ts +13 -0
- package/dist/pipeline/frameworkPlugins.d.ts.map +1 -0
- package/dist/pipeline/index.d.ts +7 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/utils/buildStats.d.ts +12 -0
- package/dist/utils/buildStats.d.ts.map +1 -0
- package/dist/utils/ensureDeps.d.ts +24 -0
- package/dist/utils/ensureDeps.d.ts.map +1 -0
- package/dist/utils/index.d.ts +11 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/prefixStream.d.ts +24 -0
- package/dist/utils/prefixStream.d.ts.map +1 -0
- package/dist/utils/version.d.ts +5 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/zipDist.d.ts +17 -0
- package/dist/utils/zipDist.d.ts.map +1 -0
- package/package.json +41 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1022 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { dirname, resolve as external_path_resolve } from "path";
|
|
3
|
+
import { createWriteStream, existsSync as external_fs_existsSync, mkdirSync, readFileSync as external_fs_readFileSync, readdirSync, statSync, watch } from "fs";
|
|
4
|
+
import { mergeRsbuildConfig } from "@rsbuild/core";
|
|
5
|
+
import { ADDFOX_ERROR_CODES, AddfoxError, HookManager, error, exitWithError, formatError, getWebExtStdoutOriginDepth, log as common_log, logDone, logDoneTimed, logDoneWithValue, setAddfoxLoggerRawWrites, warn as common_warn } from "@addfox/common";
|
|
6
|
+
import { ADDFOX_OUTPUT_ROOT, CLI_COMMANDS, CONFIG_FILES, HMR_WS_PORT, clearConfigCache, getBrowserOutputDir, getManifestRecordForTarget, getResolvedConfigFilePath, getResolvedRstestConfigFilePath, resolveAddfoxConfig, toReloadManagerEntries } from "@addfox/core";
|
|
7
|
+
import { Pipeline } from "@addfox/core/pipeline";
|
|
8
|
+
import { hmrPlugin, launchBrowserOnly } from "@addfox/rsbuild-plugin-extension-hmr";
|
|
9
|
+
import { detectFromLockfile as pkg_manager_detectFromLockfile, getAddCommand, getMissingPackages } from "@addfox/pkg-manager";
|
|
10
|
+
import { entryPlugin } from "@addfox/rsbuild-plugin-extension-entry";
|
|
11
|
+
import { extensionPlugin } from "@addfox/rsbuild-plugin-extension-manifest";
|
|
12
|
+
import { monitorPlugin } from "@addfox/rsbuild-plugin-extension-monitor";
|
|
13
|
+
import { getVueRsbuildPlugins } from "@addfox/rsbuild-plugin-vue";
|
|
14
|
+
import { spawnSync as external_child_process_spawnSync } from "child_process";
|
|
15
|
+
import archiver from "archiver";
|
|
16
|
+
import { fileURLToPath } from "url";
|
|
17
|
+
import { createRequire } from "module";
|
|
18
|
+
function expandUserPlugins(userPlugins, appRoot) {
|
|
19
|
+
const out = [];
|
|
20
|
+
const list = userPlugins ?? [];
|
|
21
|
+
const arr = Array.isArray(list) ? list : [
|
|
22
|
+
list
|
|
23
|
+
];
|
|
24
|
+
for (const p of arr){
|
|
25
|
+
const name = p?.name;
|
|
26
|
+
if ("rsbuild-plugin-vue" === name) {
|
|
27
|
+
const vuePlugins = getVueRsbuildPlugins(appRoot);
|
|
28
|
+
if (Array.isArray(vuePlugins)) out.push(...vuePlugins);
|
|
29
|
+
}
|
|
30
|
+
out.push(p);
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
function buildFrameworkPluginList(ctx) {
|
|
35
|
+
const useEntry = false !== ctx.config.entry;
|
|
36
|
+
const expanded = expandUserPlugins(ctx.config.plugins, ctx.root);
|
|
37
|
+
const useMonitor = ctx.isDev && true === ctx.config.debug;
|
|
38
|
+
const list = [];
|
|
39
|
+
if (useEntry) list.push(entryPlugin(ctx.config, ctx.entries, ctx.distPath, {
|
|
40
|
+
browser: ctx.browser
|
|
41
|
+
}));
|
|
42
|
+
list.push(...expanded);
|
|
43
|
+
if (useMonitor) list.push(monitorPlugin(ctx.config, ctx.entries, ctx.browser));
|
|
44
|
+
list.push(extensionPlugin(ctx.config, ctx.entries, ctx.browser, ctx.distPath));
|
|
45
|
+
return list;
|
|
46
|
+
}
|
|
47
|
+
class Pipeline_Pipeline {
|
|
48
|
+
hookManager;
|
|
49
|
+
corePipeline;
|
|
50
|
+
options;
|
|
51
|
+
constructor(options){
|
|
52
|
+
this.options = options;
|
|
53
|
+
this.hookManager = new HookManager();
|
|
54
|
+
this.corePipeline = new Pipeline(this.hookManager);
|
|
55
|
+
this.registerDefaultHooks();
|
|
56
|
+
}
|
|
57
|
+
get hooks() {
|
|
58
|
+
return this.hookManager;
|
|
59
|
+
}
|
|
60
|
+
async run() {
|
|
61
|
+
return this.corePipeline.execute(this.options.root, []);
|
|
62
|
+
}
|
|
63
|
+
registerDefaultHooks() {
|
|
64
|
+
this.hookManager.register('load', 'after', async (ctx)=>{
|
|
65
|
+
if (this.options.config) {
|
|
66
|
+
ctx.config = this.options.config;
|
|
67
|
+
ctx.baseEntries = this.options.baseEntries ?? [];
|
|
68
|
+
ctx.entries = this.options.entries ?? [];
|
|
69
|
+
} else {
|
|
70
|
+
const result = resolveAddfoxConfig(ctx.root);
|
|
71
|
+
ctx.config = result.config;
|
|
72
|
+
ctx.baseEntries = result.baseEntries;
|
|
73
|
+
ctx.entries = result.entries;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
this.hookManager.register('resolve', 'after', async (ctx)=>{
|
|
77
|
+
ctx.command = this.options.command;
|
|
78
|
+
ctx.browser = this.options.browser;
|
|
79
|
+
ctx.cache = this.options.cache;
|
|
80
|
+
ctx.report = this.options.report;
|
|
81
|
+
ctx.isDev = 'dev' === this.options.command;
|
|
82
|
+
const browserSubDir = getBrowserOutputDir(ctx.browser);
|
|
83
|
+
ctx.distPath = external_path_resolve(ctx.root, ctx.config.outputRoot, ctx.config.outDir, browserSubDir);
|
|
84
|
+
if (void 0 !== this.options.debug) ctx.config = {
|
|
85
|
+
...ctx.config,
|
|
86
|
+
debug: this.options.debug
|
|
87
|
+
};
|
|
88
|
+
if ('chromium' === ctx.browser) {
|
|
89
|
+
const record = getManifestRecordForTarget(ctx.config.manifest, ctx.browser);
|
|
90
|
+
if (record?.manifest_version === 2) common_warn('Warning: MV2 has been deprecated for Chrome. Please use MV3.');
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
this.hookManager.register('build', 'after', async (ctx)=>{
|
|
94
|
+
ctx.rsbuild = await this.buildRsbuildConfig(ctx);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async buildRsbuildConfig(ctx) {
|
|
98
|
+
const base = this.buildBaseRsbuildConfig(ctx);
|
|
99
|
+
const userConfig = await this.resolveUserRsbuildConfig(base, ctx.config);
|
|
100
|
+
let merged = mergeRsbuildConfig(base, userConfig);
|
|
101
|
+
if (ctx.isDev) {
|
|
102
|
+
const hmrOverrides = this.buildHmrOverrides(ctx);
|
|
103
|
+
if (hmrOverrides) merged = mergeRsbuildConfig(merged, hmrOverrides);
|
|
104
|
+
}
|
|
105
|
+
if (ctx.report) merged = await this.mergeRsdoctorPlugin(merged, ctx.root, ctx.config.outputRoot, ctx.report);
|
|
106
|
+
return merged;
|
|
107
|
+
}
|
|
108
|
+
buildBaseRsbuildConfig(ctx) {
|
|
109
|
+
const runtimeEnvConfig = buildRuntimeEnvConfig(ctx.config, ctx.browser, ctx.root, ctx.isDev);
|
|
110
|
+
const plugins = buildFrameworkPluginList(ctx);
|
|
111
|
+
const scopedProcessEnvPlugin = createScopedProcessEnvPlugin(runtimeEnvConfig.processEnvBase, runtimeEnvConfig.backgroundPrivateEnv);
|
|
112
|
+
if (scopedProcessEnvPlugin) plugins.push(scopedProcessEnvPlugin);
|
|
113
|
+
return {
|
|
114
|
+
root: ctx.root,
|
|
115
|
+
plugins,
|
|
116
|
+
source: {
|
|
117
|
+
define: runtimeEnvConfig.define
|
|
118
|
+
},
|
|
119
|
+
output: {
|
|
120
|
+
legalComments: 'none',
|
|
121
|
+
sourceMap: ctx.isDev ? {
|
|
122
|
+
js: 'inline-source-map'
|
|
123
|
+
} : false
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async resolveUserRsbuildConfig(base, config) {
|
|
128
|
+
const user = config.rsbuild;
|
|
129
|
+
if ('function' == typeof user) return user(base, {
|
|
130
|
+
merge: mergeRsbuildConfig
|
|
131
|
+
});
|
|
132
|
+
return user && 'object' == typeof user ? user : {};
|
|
133
|
+
}
|
|
134
|
+
buildHmrOverrides(ctx) {
|
|
135
|
+
const hotReload = ctx.config.hotReload;
|
|
136
|
+
const hotReloadEnabled = false !== hotReload;
|
|
137
|
+
const hotReloadOpts = 'object' == typeof hotReload && null !== hotReload ? hotReload : void 0;
|
|
138
|
+
const isConfigRestart = '1' === process.env.ADDFOX_CONFIG_RESTART;
|
|
139
|
+
const reloadManagerEntries = toReloadManagerEntries(ctx.entries, ctx.root);
|
|
140
|
+
const browserPathConfig = ctx.config.browserPath ?? {};
|
|
141
|
+
const hmrOpts = {
|
|
142
|
+
distPath: ctx.distPath,
|
|
143
|
+
autoOpen: !isConfigRestart && false !== this.options.open,
|
|
144
|
+
browser: this.options.launch,
|
|
145
|
+
debug: ctx.config.debug,
|
|
146
|
+
root: ctx.root,
|
|
147
|
+
outputRoot: ctx.config.outputRoot,
|
|
148
|
+
chromePath: browserPathConfig.chrome,
|
|
149
|
+
chromiumPath: browserPathConfig.chromium,
|
|
150
|
+
edgePath: browserPathConfig.edge,
|
|
151
|
+
bravePath: browserPathConfig.brave,
|
|
152
|
+
vivaldiPath: browserPathConfig.vivaldi,
|
|
153
|
+
operaPath: browserPathConfig.opera,
|
|
154
|
+
santaPath: browserPathConfig.santa,
|
|
155
|
+
arcPath: browserPathConfig.arc,
|
|
156
|
+
yandexPath: browserPathConfig.yandex,
|
|
157
|
+
browserosPath: browserPathConfig.browseros,
|
|
158
|
+
customPath: browserPathConfig.custom,
|
|
159
|
+
firefoxPath: browserPathConfig.firefox,
|
|
160
|
+
cache: ctx.cache,
|
|
161
|
+
wsPort: hotReloadOpts?.port ?? HMR_WS_PORT,
|
|
162
|
+
enableReload: hotReloadEnabled,
|
|
163
|
+
autoRefreshContentPage: hotReloadEnabled ? hotReloadOpts?.autoRefreshContentPage ?? true : false,
|
|
164
|
+
reloadManagerEntries
|
|
165
|
+
};
|
|
166
|
+
const useRsbuildClientHmr = 'firefox' !== ctx.browser && hotReloadEnabled;
|
|
167
|
+
const devConfig = useRsbuildClientHmr ? {
|
|
168
|
+
hmr: true,
|
|
169
|
+
client: {
|
|
170
|
+
protocol: 'ws',
|
|
171
|
+
host: '127.0.0.1',
|
|
172
|
+
port: '<port>',
|
|
173
|
+
path: '/rsbuild-hmr'
|
|
174
|
+
},
|
|
175
|
+
liveReload: true,
|
|
176
|
+
writeToDisk: true
|
|
177
|
+
} : {
|
|
178
|
+
hmr: false,
|
|
179
|
+
liveReload: false,
|
|
180
|
+
writeToDisk: true
|
|
181
|
+
};
|
|
182
|
+
return {
|
|
183
|
+
dev: devConfig,
|
|
184
|
+
server: {
|
|
185
|
+
printUrls: false,
|
|
186
|
+
cors: {
|
|
187
|
+
origin: '*'
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
plugins: [
|
|
191
|
+
hmrPlugin(hmrOpts)
|
|
192
|
+
]
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
async mergeRsdoctorPlugin(config, root, outputRoot, reportOption) {
|
|
196
|
+
const missing = getMissingPackages(root, [
|
|
197
|
+
'@rsdoctor/rspack-plugin'
|
|
198
|
+
]);
|
|
199
|
+
if (missing.length > 0) {
|
|
200
|
+
const pm = pkg_manager_detectFromLockfile(root);
|
|
201
|
+
const cmd = getAddCommand(pm, missing.join(' '), true);
|
|
202
|
+
throw new AddfoxError({
|
|
203
|
+
code: ADDFOX_ERROR_CODES.RSDOCTOR_NOT_INSTALLED,
|
|
204
|
+
message: "Rsdoctor plugin not installed",
|
|
205
|
+
details: "report (-r/--report or config.report) requires @rsdoctor/rspack-plugin",
|
|
206
|
+
hint: `Install with: ${cmd}`
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
const reportDir = external_path_resolve(root, outputRoot, 'report');
|
|
210
|
+
const { RsdoctorRspackPlugin } = await import("@rsdoctor/rspack-plugin");
|
|
211
|
+
const tools = config.tools;
|
|
212
|
+
const existing = tools?.rspack?.plugins ?? [];
|
|
213
|
+
const pluginOptions = true === reportOption ? {
|
|
214
|
+
output: {
|
|
215
|
+
reportDir
|
|
216
|
+
},
|
|
217
|
+
mode: 'brief'
|
|
218
|
+
} : {
|
|
219
|
+
...reportOption,
|
|
220
|
+
output: {
|
|
221
|
+
...reportOption.output,
|
|
222
|
+
reportDir
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const rsdoctorPlugin = new RsdoctorRspackPlugin(pluginOptions);
|
|
226
|
+
return mergeRsbuildConfig(config, {
|
|
227
|
+
tools: {
|
|
228
|
+
rspack: {
|
|
229
|
+
plugins: [
|
|
230
|
+
...existing,
|
|
231
|
+
rsdoctorPlugin
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function getManifestVersionValue(config, browser) {
|
|
239
|
+
const manifestRecord = getManifestRecordForTarget(config.manifest, browser);
|
|
240
|
+
const mv = manifestRecord?.manifest_version;
|
|
241
|
+
if ("number" == typeof mv || "string" == typeof mv) return String(mv);
|
|
242
|
+
return "";
|
|
243
|
+
}
|
|
244
|
+
function buildRuntimeEnvConfig(config, browser, root, isDev) {
|
|
245
|
+
const mode = isDev ? "development" : "production";
|
|
246
|
+
const fileEnv = loadDotEnvByMode(root, mode);
|
|
247
|
+
const mergedEnv = {
|
|
248
|
+
...fileEnv,
|
|
249
|
+
...process.env
|
|
250
|
+
};
|
|
251
|
+
const envPrefixes = getLoadEnvPrefixes(config);
|
|
252
|
+
const publicEnv = pickPublicEnvVars(mergedEnv, envPrefixes);
|
|
253
|
+
const backgroundPrivateEnv = pickBackgroundPrivateEnvVars(mergedEnv, envPrefixes);
|
|
254
|
+
const manifestVersion = getManifestVersionValue(config, browser);
|
|
255
|
+
const importMetaEnv = {
|
|
256
|
+
...publicEnv,
|
|
257
|
+
MODE: mode,
|
|
258
|
+
DEV: isDev,
|
|
259
|
+
PROD: !isDev,
|
|
260
|
+
BROWSER: browser,
|
|
261
|
+
MANIFEST_VERSION: manifestVersion
|
|
262
|
+
};
|
|
263
|
+
const processEnvForBundle = {
|
|
264
|
+
NODE_ENV: mode,
|
|
265
|
+
BROWSER: browser,
|
|
266
|
+
MANIFEST_VERSION: manifestVersion,
|
|
267
|
+
...publicEnv
|
|
268
|
+
};
|
|
269
|
+
return {
|
|
270
|
+
define: {
|
|
271
|
+
"process.env": "globalThis.__ADDFOX_PROCESS_ENV__",
|
|
272
|
+
"import.meta.env": JSON.stringify(importMetaEnv),
|
|
273
|
+
"import.meta.env.BROWSER": JSON.stringify(browser),
|
|
274
|
+
"import.meta.env.MANIFEST_VERSION": JSON.stringify(manifestVersion)
|
|
275
|
+
},
|
|
276
|
+
processEnvBase: processEnvForBundle,
|
|
277
|
+
backgroundPrivateEnv
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function getLoadEnvPrefixes(config) {
|
|
281
|
+
return config.envPrefix ?? [
|
|
282
|
+
"ADDFOX_PUBLIC_"
|
|
283
|
+
];
|
|
284
|
+
}
|
|
285
|
+
function pickPublicEnvVars(env, prefixes) {
|
|
286
|
+
const result = {};
|
|
287
|
+
for (const [key, value] of Object.entries(env))if ("string" == typeof value) {
|
|
288
|
+
if (prefixes.some((prefix)=>key.startsWith(prefix))) result[key] = value;
|
|
289
|
+
}
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
function pickBackgroundPrivateEnvVars(env, publicPrefixes) {
|
|
293
|
+
const result = {};
|
|
294
|
+
for (const [key, value] of Object.entries(env)){
|
|
295
|
+
if ("string" == typeof value) {
|
|
296
|
+
if (key.startsWith("ADDFOX_")) {
|
|
297
|
+
if (!publicPrefixes.some((prefix)=>key.startsWith(prefix))) result[key] = value;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return result;
|
|
302
|
+
}
|
|
303
|
+
function createScopedProcessEnvPlugin(processEnvBase, privateEnv) {
|
|
304
|
+
if (0 === Object.keys(processEnvBase).length) return;
|
|
305
|
+
return {
|
|
306
|
+
name: "addfox-scoped-process-env",
|
|
307
|
+
setup (api) {
|
|
308
|
+
api.modifyRsbuildConfig((config)=>{
|
|
309
|
+
const source = config.source ?? {};
|
|
310
|
+
const entry = source.entry;
|
|
311
|
+
if (!entry) return;
|
|
312
|
+
const nextEntry = {
|
|
313
|
+
...entry
|
|
314
|
+
};
|
|
315
|
+
const scopedEnvByEntry = buildScopedEnvByEntry(Object.keys(entry), processEnvBase, privateEnv);
|
|
316
|
+
for (const [entryName, entryValue] of Object.entries(entry)){
|
|
317
|
+
const scopedEnv = scopedEnvByEntry[entryName] ?? processEnvBase;
|
|
318
|
+
const snippet = buildScopedProcessEnvSnippet(scopedEnv);
|
|
319
|
+
const prepend = `data:text/javascript,${encodeURIComponent(snippet)}`;
|
|
320
|
+
nextEntry[entryName] = prependDataModule(entryValue, prepend);
|
|
321
|
+
}
|
|
322
|
+
config.source = {
|
|
323
|
+
...source,
|
|
324
|
+
entry: nextEntry
|
|
325
|
+
};
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function buildScopedEnvByEntry(entryNames, processEnvBase, privateEnv) {
|
|
331
|
+
const result = {};
|
|
332
|
+
for (const entryName of entryNames)result[entryName] = "background" === entryName ? {
|
|
333
|
+
...processEnvBase,
|
|
334
|
+
...privateEnv
|
|
335
|
+
} : {
|
|
336
|
+
...processEnvBase
|
|
337
|
+
};
|
|
338
|
+
return result;
|
|
339
|
+
}
|
|
340
|
+
function buildScopedProcessEnvSnippet(processEnv) {
|
|
341
|
+
const envJson = JSON.stringify(processEnv);
|
|
342
|
+
return `
|
|
343
|
+
const addfoxScopedEnv = ${envJson};
|
|
344
|
+
if (!globalThis.__ADDFOX_PROCESS_ENV__) {
|
|
345
|
+
globalThis.__ADDFOX_PROCESS_ENV__ = {};
|
|
346
|
+
}
|
|
347
|
+
Object.assign(globalThis.__ADDFOX_PROCESS_ENV__, addfoxScopedEnv);
|
|
348
|
+
`;
|
|
349
|
+
}
|
|
350
|
+
function prependDataModule(entryValue, prependModule) {
|
|
351
|
+
if ("string" == typeof entryValue) return {
|
|
352
|
+
import: [
|
|
353
|
+
prependModule,
|
|
354
|
+
entryValue
|
|
355
|
+
]
|
|
356
|
+
};
|
|
357
|
+
if (Array.isArray(entryValue.import)) return {
|
|
358
|
+
...entryValue,
|
|
359
|
+
import: [
|
|
360
|
+
prependModule,
|
|
361
|
+
...entryValue.import
|
|
362
|
+
]
|
|
363
|
+
};
|
|
364
|
+
return {
|
|
365
|
+
...entryValue,
|
|
366
|
+
import: [
|
|
367
|
+
prependModule,
|
|
368
|
+
entryValue.import
|
|
369
|
+
]
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
function loadDotEnvByMode(root, mode) {
|
|
373
|
+
const result = {};
|
|
374
|
+
const files = [
|
|
375
|
+
".env",
|
|
376
|
+
".env.local",
|
|
377
|
+
`.env.${mode}`,
|
|
378
|
+
`.env.${mode}.local`
|
|
379
|
+
];
|
|
380
|
+
for (const file of files){
|
|
381
|
+
const path = external_path_resolve(root, file);
|
|
382
|
+
if (!external_fs_existsSync(path)) continue;
|
|
383
|
+
const parsed = parseDotEnv(external_fs_readFileSync(path, "utf-8"));
|
|
384
|
+
Object.assign(result, parsed);
|
|
385
|
+
}
|
|
386
|
+
return result;
|
|
387
|
+
}
|
|
388
|
+
function parseDotEnv(content) {
|
|
389
|
+
const result = {};
|
|
390
|
+
const lines = content.split(/\r?\n/);
|
|
391
|
+
for (const line of lines){
|
|
392
|
+
const trimmed = line.trim();
|
|
393
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
394
|
+
const eq = trimmed.indexOf("=");
|
|
395
|
+
if (eq <= 0) continue;
|
|
396
|
+
const key = trimmed.slice(0, eq).trim();
|
|
397
|
+
const rawValue = trimmed.slice(eq + 1).trim();
|
|
398
|
+
result[key] = stripQuotes(rawValue);
|
|
399
|
+
}
|
|
400
|
+
return result;
|
|
401
|
+
}
|
|
402
|
+
function stripQuotes(value) {
|
|
403
|
+
if (value.length < 2) return value;
|
|
404
|
+
const quote = value[0];
|
|
405
|
+
if ('"' !== quote && "'" !== quote || value[value.length - 1] !== quote) return value;
|
|
406
|
+
return value.slice(1, -1);
|
|
407
|
+
}
|
|
408
|
+
async function runPipeline(options) {
|
|
409
|
+
const pipeline = new Pipeline_Pipeline(options);
|
|
410
|
+
return pipeline.run();
|
|
411
|
+
}
|
|
412
|
+
const BROWSER_FLAGS = [
|
|
413
|
+
"-b",
|
|
414
|
+
"--browser"
|
|
415
|
+
];
|
|
416
|
+
const REPORT_FLAGS = [
|
|
417
|
+
"-r",
|
|
418
|
+
"--report"
|
|
419
|
+
];
|
|
420
|
+
const BROWSER_TO_TARGET = {
|
|
421
|
+
chromium: "chromium",
|
|
422
|
+
chrome: "chromium",
|
|
423
|
+
edge: "chromium",
|
|
424
|
+
brave: "chromium",
|
|
425
|
+
vivaldi: "chromium",
|
|
426
|
+
opera: "chromium",
|
|
427
|
+
santa: "chromium",
|
|
428
|
+
arc: "chromium",
|
|
429
|
+
yandex: "chromium",
|
|
430
|
+
browseros: "chromium",
|
|
431
|
+
custom: "chromium",
|
|
432
|
+
firefox: "firefox"
|
|
433
|
+
};
|
|
434
|
+
const VALID_LAUNCH_TARGETS = new Set([
|
|
435
|
+
"chrome",
|
|
436
|
+
"chromium",
|
|
437
|
+
"edge",
|
|
438
|
+
"brave",
|
|
439
|
+
"vivaldi",
|
|
440
|
+
"opera",
|
|
441
|
+
"santa",
|
|
442
|
+
"arc",
|
|
443
|
+
"yandex",
|
|
444
|
+
"browseros",
|
|
445
|
+
"custom",
|
|
446
|
+
"firefox"
|
|
447
|
+
]);
|
|
448
|
+
class CliParser {
|
|
449
|
+
parse(argv) {
|
|
450
|
+
const cmdRaw = argv[0] ?? "dev";
|
|
451
|
+
const command = CLI_COMMANDS.includes(cmdRaw) ? cmdRaw : null;
|
|
452
|
+
if (null === command) throw new AddfoxError({
|
|
453
|
+
code: ADDFOX_ERROR_CODES.UNKNOWN_COMMAND,
|
|
454
|
+
message: "Unknown command",
|
|
455
|
+
details: `Command: "${cmdRaw}"`,
|
|
456
|
+
hint: "Supported: addfox dev | addfox build | addfox test [-b chrome|...]; custom requires browser.custom in config"
|
|
457
|
+
});
|
|
458
|
+
const { browser, launch, unknown: unknownBrowser } = this.getBrowserFromArgv(argv);
|
|
459
|
+
const cache = this.getCacheFromArgv(argv);
|
|
460
|
+
const debug = this.getDebugFromArgv(argv);
|
|
461
|
+
const report = this.getReportFromArgv(argv);
|
|
462
|
+
const open = this.getOpenFromArgv(argv);
|
|
463
|
+
return {
|
|
464
|
+
command,
|
|
465
|
+
browser,
|
|
466
|
+
launch,
|
|
467
|
+
unknownBrowser,
|
|
468
|
+
cache,
|
|
469
|
+
debug,
|
|
470
|
+
report,
|
|
471
|
+
open
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
getBrowserFromArgv(argv) {
|
|
475
|
+
for(let i = 0; i < argv.length; i++){
|
|
476
|
+
const arg = argv[i];
|
|
477
|
+
if (BROWSER_FLAGS.includes(arg)) {
|
|
478
|
+
const value = argv[i + 1];
|
|
479
|
+
if (value && !value.startsWith("-")) {
|
|
480
|
+
const normalized = value.trim().toLowerCase();
|
|
481
|
+
const browser = BROWSER_TO_TARGET[normalized];
|
|
482
|
+
if (browser) {
|
|
483
|
+
const launch = VALID_LAUNCH_TARGETS.has(normalized) ? normalized : void 0;
|
|
484
|
+
return {
|
|
485
|
+
browser,
|
|
486
|
+
launch
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
unknown: value
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (arg.startsWith("-b=") || arg.startsWith("--browser=")) {
|
|
495
|
+
const normalized = (arg.split("=")[1] ?? "").trim().toLowerCase();
|
|
496
|
+
const browser = BROWSER_TO_TARGET[normalized];
|
|
497
|
+
if (browser) {
|
|
498
|
+
const launch = VALID_LAUNCH_TARGETS.has(normalized) ? normalized : void 0;
|
|
499
|
+
return {
|
|
500
|
+
browser,
|
|
501
|
+
launch
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
unknown: normalized
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return {};
|
|
510
|
+
}
|
|
511
|
+
getCacheFromArgv(argv) {
|
|
512
|
+
let cache;
|
|
513
|
+
for (const arg of argv){
|
|
514
|
+
if ("-c" === arg || "--cache" === arg) cache = true;
|
|
515
|
+
if ("--no-cache" === arg) cache = false;
|
|
516
|
+
}
|
|
517
|
+
return cache;
|
|
518
|
+
}
|
|
519
|
+
getDebugFromArgv(argv) {
|
|
520
|
+
return argv.some((arg)=>"--debug" === arg) ? true : void 0;
|
|
521
|
+
}
|
|
522
|
+
getReportFromArgv(argv) {
|
|
523
|
+
return argv.some((arg)=>REPORT_FLAGS.includes(arg)) ? true : void 0;
|
|
524
|
+
}
|
|
525
|
+
getOpenFromArgv(argv) {
|
|
526
|
+
return !argv.some((arg)=>"--no-open" === arg);
|
|
527
|
+
}
|
|
528
|
+
assertSupportedBrowser(value) {
|
|
529
|
+
const b = BROWSER_TO_TARGET[value.trim().toLowerCase()];
|
|
530
|
+
if (b) return;
|
|
531
|
+
throw new AddfoxError({
|
|
532
|
+
code: ADDFOX_ERROR_CODES.INVALID_BROWSER,
|
|
533
|
+
message: "Unsupported browser argument",
|
|
534
|
+
details: `Current value: "${value}"`,
|
|
535
|
+
hint: "Use -b chrome/chromium/edge/brave/vivaldi/opera/santa/arc/yandex/browseros/custom/firefox or --browser=...; use custom only with browser.custom in config"
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
const defaultParser = new CliParser();
|
|
540
|
+
function parseCliArgs(argv) {
|
|
541
|
+
return defaultParser.parse(argv);
|
|
542
|
+
}
|
|
543
|
+
function getDistSizeSync(dirPath) {
|
|
544
|
+
if (!external_fs_existsSync(dirPath)) return -1;
|
|
545
|
+
const stat = statSync(dirPath);
|
|
546
|
+
if (!stat.isDirectory()) return stat.size;
|
|
547
|
+
let total = 0;
|
|
548
|
+
for (const name of readdirSync(dirPath)){
|
|
549
|
+
const s = statSync(external_path_resolve(dirPath, name));
|
|
550
|
+
total += s.isDirectory() ? getDistSizeSync(external_path_resolve(dirPath, name)) : s.size;
|
|
551
|
+
}
|
|
552
|
+
return total;
|
|
553
|
+
}
|
|
554
|
+
function formatBytes(bytes) {
|
|
555
|
+
if (bytes < 1024) return bytes + " B";
|
|
556
|
+
if (bytes < 1048576) return (bytes / 1024).toFixed(2) + " KB";
|
|
557
|
+
return (bytes / 1048576).toFixed(2) + " MB";
|
|
558
|
+
}
|
|
559
|
+
function isSourceMapEnabled(rsbuildConfig) {
|
|
560
|
+
const sm = rsbuildConfig?.output?.sourceMap;
|
|
561
|
+
if (true === sm) return true;
|
|
562
|
+
if (sm && "object" == typeof sm && "string" == typeof sm.js) return true;
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
function getBuildOutputSize(result) {
|
|
566
|
+
const stats = result?.stats;
|
|
567
|
+
if (!stats?.toJson) return null;
|
|
568
|
+
try {
|
|
569
|
+
const json = stats.toJson({
|
|
570
|
+
all: false,
|
|
571
|
+
assets: true
|
|
572
|
+
});
|
|
573
|
+
const assets = json?.assets;
|
|
574
|
+
if (!Array.isArray(assets)) return null;
|
|
575
|
+
let total = 0;
|
|
576
|
+
for (const a of assets)if (a && "number" == typeof a.size) total += a.size;
|
|
577
|
+
return total > 0 ? total : null;
|
|
578
|
+
} catch {
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
const ADDFOX_PREFIX = "\x1b[38;5;208m[Addfox]\x1b[0m ";
|
|
583
|
+
const RSBUILD_PREFIX = "\x1b[38;5;141m[Rsbuild]\x1b[0m ";
|
|
584
|
+
const WEBEXT_PREFIX = "\x1b[38;5;45m[Web-ext]\x1b[0m ";
|
|
585
|
+
let rawStdoutWrite = null;
|
|
586
|
+
let rawStderrWrite = null;
|
|
587
|
+
let outputPrefix = "addfox";
|
|
588
|
+
function setOutputPrefixRsbuild() {
|
|
589
|
+
outputPrefix = "rsbuild";
|
|
590
|
+
}
|
|
591
|
+
function setOutputPrefixAddfox() {
|
|
592
|
+
outputPrefix = "addfox";
|
|
593
|
+
}
|
|
594
|
+
function getRawWrites() {
|
|
595
|
+
return {
|
|
596
|
+
stdout: rawStdoutWrite ?? process.stdout.write.bind(process.stdout),
|
|
597
|
+
stderr: rawStderrWrite ?? process.stderr.write.bind(process.stderr)
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
function isEncoding(x) {
|
|
601
|
+
return "string" == typeof x;
|
|
602
|
+
}
|
|
603
|
+
function createPrefixedWrite(stream, getPrefix) {
|
|
604
|
+
const originalWrite = stream.write.bind(stream);
|
|
605
|
+
let buffer = "";
|
|
606
|
+
function flushIncomplete() {
|
|
607
|
+
if (buffer.length > 0) {
|
|
608
|
+
originalWrite(getPrefix(buffer) + buffer);
|
|
609
|
+
buffer = "";
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
const write = function(chunk, encodingOrCallback, callback) {
|
|
613
|
+
const encoding = isEncoding(encodingOrCallback) ? encodingOrCallback : void 0;
|
|
614
|
+
const cb = "function" == typeof encodingOrCallback ? encodingOrCallback : callback;
|
|
615
|
+
const str = "string" == typeof chunk ? chunk : chunk.toString(encoding ?? "utf8");
|
|
616
|
+
buffer += str;
|
|
617
|
+
const lines = buffer.split("\n");
|
|
618
|
+
buffer = lines.pop() ?? "";
|
|
619
|
+
for (const line of lines)originalWrite(getPrefix(line) + line + "\n", encoding);
|
|
620
|
+
if ("function" == typeof cb) cb();
|
|
621
|
+
return true;
|
|
622
|
+
};
|
|
623
|
+
write.flush = flushIncomplete;
|
|
624
|
+
return write;
|
|
625
|
+
}
|
|
626
|
+
function wrapAddfoxOutput() {
|
|
627
|
+
rawStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
628
|
+
rawStderrWrite = process.stderr.write.bind(process.stderr);
|
|
629
|
+
const getPrefix = (_line)=>{
|
|
630
|
+
if ("rsbuild" === outputPrefix && getWebExtStdoutOriginDepth() > 0) return WEBEXT_PREFIX;
|
|
631
|
+
return "rsbuild" === outputPrefix ? RSBUILD_PREFIX : ADDFOX_PREFIX;
|
|
632
|
+
};
|
|
633
|
+
const stdoutWrite = createPrefixedWrite(process.stdout, getPrefix);
|
|
634
|
+
const stderrWrite = createPrefixedWrite(process.stderr, getPrefix);
|
|
635
|
+
process.stdout.write = stdoutWrite;
|
|
636
|
+
process.stderr.write = stderrWrite;
|
|
637
|
+
const flush = ()=>{
|
|
638
|
+
const fOut = stdoutWrite.flush;
|
|
639
|
+
const fErr = stderrWrite.flush;
|
|
640
|
+
if (fOut) fOut();
|
|
641
|
+
if (fErr) fErr();
|
|
642
|
+
};
|
|
643
|
+
process.once("exit", flush);
|
|
644
|
+
}
|
|
645
|
+
const ZIP_OUTPUT_CODE = "ADDFOX_ZIP_OUTPUT";
|
|
646
|
+
const ZIP_ARCHIVE_CODE = "ADDFOX_ZIP_ARCHIVE";
|
|
647
|
+
const ZIP_LEVEL = 9;
|
|
648
|
+
function zipDist(distPath, root, outDir, browser, deps) {
|
|
649
|
+
const createStream = deps?.createWriteStream ?? createWriteStream;
|
|
650
|
+
const archiverFn = deps?.archiver ?? archiver;
|
|
651
|
+
const mkdir = deps?.mkdirSync ?? mkdirSync;
|
|
652
|
+
const exists = deps?.existsSync ?? external_fs_existsSync;
|
|
653
|
+
const outputRoot = ADDFOX_OUTPUT_ROOT;
|
|
654
|
+
const zipDir = external_path_resolve(root, outputRoot, outDir);
|
|
655
|
+
if (!exists(zipDir)) mkdir(zipDir, {
|
|
656
|
+
recursive: true
|
|
657
|
+
});
|
|
658
|
+
const browserSuffix = browser ? `-${browser}` : "";
|
|
659
|
+
const zipPath = external_path_resolve(zipDir, `${outDir}${browserSuffix}.zip`);
|
|
660
|
+
const output = createStream(zipPath);
|
|
661
|
+
const archive = archiverFn("zip", {
|
|
662
|
+
zlib: {
|
|
663
|
+
level: ZIP_LEVEL
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
return new Promise((resolvePromise, reject)=>{
|
|
667
|
+
output.on("error", (err)=>reject(new AddfoxError({
|
|
668
|
+
message: "Zip output stream failed",
|
|
669
|
+
code: ZIP_OUTPUT_CODE,
|
|
670
|
+
details: err instanceof Error ? err.message : String(err),
|
|
671
|
+
cause: err
|
|
672
|
+
})));
|
|
673
|
+
output.on("close", ()=>resolvePromise(zipPath));
|
|
674
|
+
archive.on("error", (err)=>reject(new AddfoxError({
|
|
675
|
+
message: "Zip archive failed",
|
|
676
|
+
code: ZIP_ARCHIVE_CODE,
|
|
677
|
+
details: err instanceof Error ? err.message : String(err),
|
|
678
|
+
cause: err
|
|
679
|
+
})));
|
|
680
|
+
archive.pipe(output);
|
|
681
|
+
archive.directory(distPath, false);
|
|
682
|
+
archive.finalize();
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
const version_require = createRequire(import.meta.url);
|
|
686
|
+
function readPackageVersion(pkgPath) {
|
|
687
|
+
try {
|
|
688
|
+
const pkg = JSON.parse(external_fs_readFileSync(pkgPath, "utf-8"));
|
|
689
|
+
return pkg.version ?? "?";
|
|
690
|
+
} catch {
|
|
691
|
+
return "?";
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
function getVersion() {
|
|
695
|
+
try {
|
|
696
|
+
const pkgPath = external_path_resolve(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
697
|
+
return readPackageVersion(pkgPath) || "0.0.0";
|
|
698
|
+
} catch {
|
|
699
|
+
return "0.0.0";
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
function getRsbuildVersion(projectRoot) {
|
|
703
|
+
const cliDir = dirname(fileURLToPath(import.meta.url));
|
|
704
|
+
const candidates = [
|
|
705
|
+
()=>{
|
|
706
|
+
const p = external_path_resolve(projectRoot, "node_modules", "@rsbuild", "core", "package.json");
|
|
707
|
+
return external_fs_existsSync(p) ? p : null;
|
|
708
|
+
},
|
|
709
|
+
()=>{
|
|
710
|
+
try {
|
|
711
|
+
return version_require.resolve("@rsbuild/core/package.json", {
|
|
712
|
+
paths: [
|
|
713
|
+
projectRoot
|
|
714
|
+
]
|
|
715
|
+
});
|
|
716
|
+
} catch {
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
},
|
|
720
|
+
()=>{
|
|
721
|
+
try {
|
|
722
|
+
return version_require.resolve("@rsbuild/core/package.json", {
|
|
723
|
+
paths: [
|
|
724
|
+
external_path_resolve(cliDir, ".."),
|
|
725
|
+
external_path_resolve(cliDir, "..", "..")
|
|
726
|
+
]
|
|
727
|
+
});
|
|
728
|
+
} catch {
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
];
|
|
733
|
+
for (const getPath of candidates){
|
|
734
|
+
const pkgPath = getPath();
|
|
735
|
+
if (pkgPath) return readPackageVersion(pkgPath);
|
|
736
|
+
}
|
|
737
|
+
return "?";
|
|
738
|
+
}
|
|
739
|
+
const test_require = createRequire(import.meta.url);
|
|
740
|
+
function hasRstestConfigFile(projectRoot) {
|
|
741
|
+
return null !== getResolvedRstestConfigFilePath(projectRoot);
|
|
742
|
+
}
|
|
743
|
+
function isRstestBrowserEnabled(projectRoot) {
|
|
744
|
+
const configPath = getResolvedRstestConfigFilePath(projectRoot);
|
|
745
|
+
if (!configPath) return false;
|
|
746
|
+
try {
|
|
747
|
+
const content = external_fs_readFileSync(configPath, "utf-8");
|
|
748
|
+
return /browser\s*:\s*\{[^}]*enabled\s*:\s*true/.test(content) || /browser\.enabled\s*===?\s*true/.test(content);
|
|
749
|
+
} catch {
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
function getRequiredTestPackages(projectRoot) {
|
|
754
|
+
const required = [
|
|
755
|
+
"@rstest/core"
|
|
756
|
+
];
|
|
757
|
+
if (isRstestBrowserEnabled(projectRoot)) required.push("@rstest/browser", "playwright");
|
|
758
|
+
return required;
|
|
759
|
+
}
|
|
760
|
+
function getRstestBinPath(projectRoot) {
|
|
761
|
+
const binDir = external_path_resolve(projectRoot, "node_modules", ".bin");
|
|
762
|
+
const name = "win32" === process.platform ? "rstest.cmd" : "rstest";
|
|
763
|
+
const p = external_path_resolve(binDir, name);
|
|
764
|
+
if (external_fs_existsSync(p)) return p;
|
|
765
|
+
const fallback = external_path_resolve(binDir, "rstest");
|
|
766
|
+
if (external_fs_existsSync(fallback)) return fallback;
|
|
767
|
+
try {
|
|
768
|
+
const corePkgPath = test_require.resolve("@rstest/core/package.json", {
|
|
769
|
+
paths: [
|
|
770
|
+
projectRoot
|
|
771
|
+
]
|
|
772
|
+
});
|
|
773
|
+
const coreDir = dirname(corePkgPath);
|
|
774
|
+
const binInCore = external_path_resolve(coreDir, "bin", "rstest.js");
|
|
775
|
+
if (external_fs_existsSync(binInCore)) return binInCore;
|
|
776
|
+
} catch {}
|
|
777
|
+
exitWithError(new Error("rstest binary not found in node_modules/.bin after installing @rstest/core"));
|
|
778
|
+
}
|
|
779
|
+
function runRstest(projectRoot, restArgs) {
|
|
780
|
+
const rstestBin = getRstestBinPath(projectRoot);
|
|
781
|
+
const result = external_child_process_spawnSync(rstestBin, restArgs, {
|
|
782
|
+
cwd: projectRoot,
|
|
783
|
+
stdio: "inherit",
|
|
784
|
+
shell: "win32" === process.platform
|
|
785
|
+
});
|
|
786
|
+
return result.status ?? 0;
|
|
787
|
+
}
|
|
788
|
+
async function runTest(projectRoot, argv) {
|
|
789
|
+
if (!hasRstestConfigFile(projectRoot)) {
|
|
790
|
+
const files = "rstest.config.cts, rstest.config.mts, rstest.config.cjs, rstest.config.js, rstest.config.ts, rstest.config.mjs";
|
|
791
|
+
throw new AddfoxError({
|
|
792
|
+
code: ADDFOX_ERROR_CODES.RSTEST_CONFIG_NOT_FOUND,
|
|
793
|
+
message: "Rstest config file not found",
|
|
794
|
+
details: `No rstest.config.* found under ${projectRoot}`,
|
|
795
|
+
hint: `Create one of: ${files}`
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
const required = getRequiredTestPackages(projectRoot);
|
|
799
|
+
const missing = getMissingPackages(projectRoot, required);
|
|
800
|
+
if (missing.length > 0) {
|
|
801
|
+
const pm = pkg_manager_detectFromLockfile(projectRoot);
|
|
802
|
+
const installCmd = getAddCommand(pm, missing.join(" "), true);
|
|
803
|
+
exitWithError(new Error(`Missing test dependencies: ${missing.join(", ")}. Please run:\n ${installCmd}`));
|
|
804
|
+
}
|
|
805
|
+
const restArgs = argv.slice(1);
|
|
806
|
+
const code = runRstest(projectRoot, restArgs);
|
|
807
|
+
process.exit(code);
|
|
808
|
+
}
|
|
809
|
+
const cli_root = process.cwd();
|
|
810
|
+
const PURPLE = "\x1b[38;5;141m";
|
|
811
|
+
const RESET = "\x1b[0m";
|
|
812
|
+
function hasConfigFile() {
|
|
813
|
+
return CONFIG_FILES.some((file)=>external_fs_existsSync(external_path_resolve(cli_root, file)));
|
|
814
|
+
}
|
|
815
|
+
function printHelp() {
|
|
816
|
+
const version = getVersion();
|
|
817
|
+
console.log(`
|
|
818
|
+
addfox v${version}
|
|
819
|
+
|
|
820
|
+
Build tool for browser extensions
|
|
821
|
+
|
|
822
|
+
Usage:
|
|
823
|
+
addfox <command> [options]
|
|
824
|
+
|
|
825
|
+
Commands:
|
|
826
|
+
dev Start development server with HMR
|
|
827
|
+
build Build for production
|
|
828
|
+
test Run tests with rstest (unit + optional E2E); forwards args to rstest
|
|
829
|
+
|
|
830
|
+
Options:
|
|
831
|
+
-b, --browser <browser> Target/launch browser (chromium | firefox | chrome | edge | brave | ...)
|
|
832
|
+
-c, --cache Cache browser profile between launches
|
|
833
|
+
--no-cache Disable browser profile cache for current run
|
|
834
|
+
-r, --report Enable Rsdoctor build report (opens analysis after build)
|
|
835
|
+
--no-open Do not auto-open browser (dev/build)
|
|
836
|
+
--debug Enable debug mode
|
|
837
|
+
--help Show this help message
|
|
838
|
+
--version Show version number
|
|
839
|
+
`);
|
|
840
|
+
}
|
|
841
|
+
function resolveOptions(argv, config) {
|
|
842
|
+
const parsed = parseCliArgs(argv);
|
|
843
|
+
const browser = parsed.browser ?? "chromium";
|
|
844
|
+
const launch = parsed.launch ?? ("firefox" === browser ? "firefox" : "chrome");
|
|
845
|
+
const debug = parsed.debug ?? config.debug ?? false;
|
|
846
|
+
return {
|
|
847
|
+
browser,
|
|
848
|
+
launch,
|
|
849
|
+
cache: parsed.cache ?? config.cache ?? true,
|
|
850
|
+
report: parsed.report ?? false,
|
|
851
|
+
debug,
|
|
852
|
+
open: parsed.open ?? true
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
async function createRsbuildInstance(ctx) {
|
|
856
|
+
setOutputPrefixRsbuild();
|
|
857
|
+
const { createRsbuild } = await import("@rsbuild/core");
|
|
858
|
+
const rsbuild = await createRsbuild({
|
|
859
|
+
rsbuildConfig: ctx.rsbuild,
|
|
860
|
+
cwd: ctx.root,
|
|
861
|
+
loadEnv: {
|
|
862
|
+
cwd: ctx.root,
|
|
863
|
+
prefixes: getLoadEnvPrefixes(ctx.config)
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
return rsbuild;
|
|
867
|
+
}
|
|
868
|
+
function logExtensionSize(distDir, rsbuildConfig) {
|
|
869
|
+
const size = getDistSizeSync(distDir);
|
|
870
|
+
if (size < 0) return;
|
|
871
|
+
const sizeStr = formatBytes(size);
|
|
872
|
+
const suffix = isSourceMapEnabled(rsbuildConfig) ? " (with inline-source-map)" : "";
|
|
873
|
+
logDoneWithValue("Extension size:", sizeStr + suffix);
|
|
874
|
+
}
|
|
875
|
+
const ADDFOX_CONFIG_DEBOUNCE_MS = 300;
|
|
876
|
+
function watchAddfoxConfig(configPath, onRestart) {
|
|
877
|
+
let timer = null;
|
|
878
|
+
const run = ()=>{
|
|
879
|
+
if (timer) clearTimeout(timer);
|
|
880
|
+
timer = setTimeout(()=>{
|
|
881
|
+
timer = null;
|
|
882
|
+
Promise.resolve(onRestart()).catch((e)=>{
|
|
883
|
+
error(formatError(e));
|
|
884
|
+
exitWithError(e);
|
|
885
|
+
});
|
|
886
|
+
}, ADDFOX_CONFIG_DEBOUNCE_MS);
|
|
887
|
+
};
|
|
888
|
+
try {
|
|
889
|
+
const watcher = watch(configPath, {
|
|
890
|
+
persistent: true
|
|
891
|
+
}, (eventType, filename)=>{
|
|
892
|
+
if (filename && ("change" === eventType || "rename" === eventType)) run();
|
|
893
|
+
});
|
|
894
|
+
return {
|
|
895
|
+
close () {
|
|
896
|
+
try {
|
|
897
|
+
watcher.close();
|
|
898
|
+
} catch {}
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
} catch {
|
|
902
|
+
return {
|
|
903
|
+
close () {}
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
async function runDev(root, argv) {
|
|
908
|
+
const { config, baseEntries, entries } = resolveAddfoxConfig(root);
|
|
909
|
+
const resolved = resolveOptions(argv, config);
|
|
910
|
+
const options = {
|
|
911
|
+
root,
|
|
912
|
+
command: 'dev',
|
|
913
|
+
...resolved,
|
|
914
|
+
config,
|
|
915
|
+
baseEntries,
|
|
916
|
+
entries
|
|
917
|
+
};
|
|
918
|
+
const rsbuildReadyStart = performance.now();
|
|
919
|
+
const ctx = await runPipeline(options);
|
|
920
|
+
process.env.NODE_ENV = ctx.isDev ? "development" : "production";
|
|
921
|
+
const rsbuild = await createRsbuildInstance(ctx);
|
|
922
|
+
logDoneTimed("Rsbuild ready", Math.round(performance.now() - rsbuildReadyStart));
|
|
923
|
+
const configPath = getResolvedConfigFilePath(ctx.root);
|
|
924
|
+
let devServerRef = null;
|
|
925
|
+
let watcherRef = null;
|
|
926
|
+
const onAddfoxConfigChange = async ()=>{
|
|
927
|
+
if (watcherRef) {
|
|
928
|
+
watcherRef.close();
|
|
929
|
+
watcherRef = null;
|
|
930
|
+
}
|
|
931
|
+
if (devServerRef?.server?.close) await devServerRef.server.close();
|
|
932
|
+
if (configPath) clearConfigCache(configPath);
|
|
933
|
+
process.env.ADDFOX_CONFIG_RESTART = "1";
|
|
934
|
+
try {
|
|
935
|
+
await runDev(root, argv);
|
|
936
|
+
} finally{
|
|
937
|
+
delete process.env.ADDFOX_CONFIG_RESTART;
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
if (configPath) watcherRef = watchAddfoxConfig(configPath, onAddfoxConfigChange);
|
|
941
|
+
const devServerStart = performance.now();
|
|
942
|
+
devServerRef = await rsbuild.startDevServer({
|
|
943
|
+
getPortSilently: true
|
|
944
|
+
});
|
|
945
|
+
const urls = devServerRef?.urls ?? [];
|
|
946
|
+
const mainUrl = urls[0] ?? `http://localhost:${devServerRef?.port ?? "?"}`;
|
|
947
|
+
logDoneTimed("Dev server " + mainUrl, Math.round(performance.now() - devServerStart));
|
|
948
|
+
const devDistDir = rsbuild.context?.distPath ?? ctx.distPath;
|
|
949
|
+
setTimeout(()=>logExtensionSize(devDistDir, ctx.rsbuild), 2500);
|
|
950
|
+
}
|
|
951
|
+
async function runBuild(root, argv) {
|
|
952
|
+
const { config, baseEntries, entries } = resolveAddfoxConfig(root);
|
|
953
|
+
const resolved = resolveOptions(argv, config);
|
|
954
|
+
const options = {
|
|
955
|
+
root,
|
|
956
|
+
command: 'build',
|
|
957
|
+
...resolved,
|
|
958
|
+
config,
|
|
959
|
+
baseEntries,
|
|
960
|
+
entries
|
|
961
|
+
};
|
|
962
|
+
const rsbuildReadyStart = performance.now();
|
|
963
|
+
const ctx = await runPipeline(options);
|
|
964
|
+
const rsbuild = await createRsbuildInstance(ctx);
|
|
965
|
+
logDoneTimed("Rsbuild ready", Math.round(performance.now() - rsbuildReadyStart));
|
|
966
|
+
const buildStart = performance.now();
|
|
967
|
+
const buildResult = await rsbuild.build();
|
|
968
|
+
logDoneTimed("Rsbuild build", Math.round(performance.now() - buildStart));
|
|
969
|
+
setOutputPrefixAddfox();
|
|
970
|
+
if (false !== ctx.config.zip) {
|
|
971
|
+
const zipPath = await zipDist(ctx.distPath, ctx.root, ctx.config.outDir, ctx.browser);
|
|
972
|
+
logDone("Zipped output to", zipPath);
|
|
973
|
+
}
|
|
974
|
+
const distDir = rsbuild.context?.distPath || ctx.distPath;
|
|
975
|
+
const distSize = getBuildOutputSize(buildResult) ?? getDistSizeSync(distDir);
|
|
976
|
+
if (distSize >= 0) logDoneWithValue("Extension size:", formatBytes(distSize));
|
|
977
|
+
if (resolved.open) {
|
|
978
|
+
const browserPathConfig = ctx.config.browserPath ?? {};
|
|
979
|
+
await launchBrowserOnly({
|
|
980
|
+
distPath: distDir,
|
|
981
|
+
browser: resolved.launch,
|
|
982
|
+
cache: resolved.cache,
|
|
983
|
+
chromePath: browserPathConfig.chrome,
|
|
984
|
+
chromiumPath: browserPathConfig.chromium,
|
|
985
|
+
edgePath: browserPathConfig.edge,
|
|
986
|
+
bravePath: browserPathConfig.brave,
|
|
987
|
+
vivaldiPath: browserPathConfig.vivaldi,
|
|
988
|
+
operaPath: browserPathConfig.opera,
|
|
989
|
+
santaPath: browserPathConfig.santa,
|
|
990
|
+
arcPath: browserPathConfig.arc,
|
|
991
|
+
yandexPath: browserPathConfig.yandex,
|
|
992
|
+
browserosPath: browserPathConfig.browseros,
|
|
993
|
+
customPath: browserPathConfig.custom,
|
|
994
|
+
firefoxPath: browserPathConfig.firefox
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
async function main() {
|
|
999
|
+
const argv = process.argv.slice(2);
|
|
1000
|
+
if (argv.includes("--help") || argv.includes("-h")) return void printHelp();
|
|
1001
|
+
if (argv.includes("--version") || argv.includes("-v")) return void console.log(getVersion());
|
|
1002
|
+
wrapAddfoxOutput();
|
|
1003
|
+
setAddfoxLoggerRawWrites(getRawWrites());
|
|
1004
|
+
common_log("Addfox " + getVersion() + " with " + PURPLE + "Rsbuild " + getRsbuildVersion(cli_root) + RESET);
|
|
1005
|
+
const command = argv[0];
|
|
1006
|
+
if ("test" === command) return void await runTest(cli_root, argv);
|
|
1007
|
+
if (!hasConfigFile()) throw new AddfoxError({
|
|
1008
|
+
code: ADDFOX_ERROR_CODES.CONFIG_NOT_FOUND,
|
|
1009
|
+
message: "Addfox config file not found",
|
|
1010
|
+
details: `No addfox.config.ts, addfox.config.js or addfox.config.mjs found under ${cli_root}`,
|
|
1011
|
+
hint: "Run the command from project root or create addfox.config.ts / addfox.config.js"
|
|
1012
|
+
});
|
|
1013
|
+
process.env.NODE_ENV = "dev" === command ? "development" : "production";
|
|
1014
|
+
if ("dev" === command) await runDev(cli_root, argv);
|
|
1015
|
+
else await runBuild(cli_root, argv);
|
|
1016
|
+
}
|
|
1017
|
+
main().catch((e)=>{
|
|
1018
|
+
error(formatError(e));
|
|
1019
|
+
exitWithError(e);
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
//# sourceMappingURL=cli.js.map
|