this repo has no description
1/* eslint-disable no-console */
2import * as esbuild from "esbuild";
3import copyStaticFiles from "esbuild-copy-static-files";
4
5import path from "path";
6import fs from "fs";
7
8const config = {
9 injector: "packages/injector/src/index.ts",
10 "node-preload": "packages/node-preload/src/index.ts",
11 "web-preload": "packages/web-preload/src/index.ts"
12};
13
14const prod = process.env.NODE_ENV === "production";
15const watch = process.argv.includes("--watch");
16const browser = process.argv.includes("--browser");
17const mv2 = process.argv.includes("--mv2");
18
19const buildBranch = process.env.MOONLIGHT_BRANCH ?? "dev";
20const buildVersion = process.env.MOONLIGHT_VERSION ?? "dev";
21
22const external = [
23 "electron",
24 "fs",
25 "path",
26 "module",
27 "discord", // mappings
28
29 // Silence an esbuild warning
30 "./node-preload.js"
31];
32
33let lastMessages = new Set();
34/** @type {import("esbuild").Plugin} */
35const deduplicatedLogging = {
36 name: "deduplicated-logging",
37 setup(build) {
38 build.onStart(() => {
39 lastMessages.clear();
40 });
41
42 build.onEnd(async (result) => {
43 const formatted = await Promise.all([
44 esbuild.formatMessages(result.warnings, {
45 kind: "warning",
46 color: true
47 }),
48 esbuild.formatMessages(result.errors, { kind: "error", color: true })
49 ]).then((a) => a.flat());
50
51 // console.log(formatted);
52 for (const message of formatted) {
53 if (lastMessages.has(message)) continue;
54 lastMessages.add(message);
55 console.log(message.trim());
56 }
57 });
58 }
59};
60
61const timeFormatter = new Intl.DateTimeFormat(undefined, {
62 hour: "numeric",
63 minute: "numeric",
64 second: "numeric",
65 hour12: false
66});
67/** @type {import("esbuild").Plugin} */
68const taggedBuildLog = (tag) => ({
69 name: "build-log",
70 setup(build) {
71 build.onEnd((result) => {
72 console.log(
73 `[${timeFormatter.format(new Date())}] [${tag}] build finished`
74 );
75 });
76 }
77});
78
79async function build(name, entry) {
80 let outfile = path.join("./dist", name + ".js");
81 const browserDir = mv2 ? "browser-mv2" : "browser";
82 if (name === "browser") outfile = path.join("./dist", browserDir, "index.js");
83
84 const dropLabels = [];
85 const labels = {
86 injector: ["injector"],
87 nodePreload: ["node-preload"],
88 webPreload: ["web-preload"],
89 browser: ["browser"],
90
91 webTarget: ["web-preload", "browser"],
92 nodeTarget: ["node-preload", "injector"]
93 };
94 for (const [label, targets] of Object.entries(labels)) {
95 if (!targets.includes(name)) {
96 dropLabels.push(label);
97 }
98 }
99
100 const define = {
101 MOONLIGHT_ENV: `"${name}"`,
102 MOONLIGHT_PROD: prod.toString(),
103 MOONLIGHT_BRANCH: `"${buildBranch}"`,
104 MOONLIGHT_VERSION: `"${buildVersion}"`
105 };
106
107 for (const iterName of [
108 "injector",
109 "node-preload",
110 "web-preload",
111 "browser"
112 ]) {
113 const snake = iterName.replace(/-/g, "_").toUpperCase();
114 define[`MOONLIGHT_${snake}`] = (name === iterName).toString();
115 }
116
117 const nodeDependencies = ["glob"];
118 const ignoredExternal = name === "web-preload" ? nodeDependencies : [];
119
120 const plugins = [deduplicatedLogging, taggedBuildLog(name)];
121 if (name === "browser") {
122 plugins.push(
123 copyStaticFiles({
124 src: mv2
125 ? "./packages/browser/manifestv2.json"
126 : "./packages/browser/manifest.json",
127 dest: `./dist/${browserDir}/manifest.json`
128 })
129 );
130
131 if (!mv2) {
132 plugins.push(
133 copyStaticFiles({
134 src: "./packages/browser/modifyResponseHeaders.json",
135 dest: `./dist/${browserDir}/modifyResponseHeaders.json`
136 })
137 );
138 plugins.push(
139 copyStaticFiles({
140 src: "./packages/browser/blockLoading.json",
141 dest: `./dist/${browserDir}/blockLoading.json`
142 })
143 );
144 }
145
146 plugins.push(
147 copyStaticFiles({
148 src: mv2
149 ? "./packages/browser/src/background-mv2.js"
150 : "./packages/browser/src/background.js",
151 dest: `./dist/${browserDir}/background.js`
152 })
153 );
154 }
155
156 /** @type {import("esbuild").BuildOptions} */
157 const esbuildConfig = {
158 entryPoints: [entry],
159 outfile,
160
161 format: "iife",
162 globalName: "module.exports",
163
164 platform: ["web-preload", "browser"].includes(name) ? "browser" : "node",
165
166 treeShaking: true,
167 bundle: true,
168 minify: prod,
169 sourcemap: "inline",
170
171 external: [...ignoredExternal, ...external],
172
173 define,
174 dropLabels,
175
176 logLevel: "silent",
177 plugins
178 };
179
180 if (name === "browser") {
181 const coreExtensionsJson = {};
182
183 // eslint-disable-next-line no-inner-declarations
184 function readDir(dir) {
185 const files = fs.readdirSync(dir);
186 for (const file of files) {
187 const filePath = dir + "/" + file;
188 const normalizedPath = filePath.replace("./dist/core-extensions/", "");
189 if (fs.statSync(filePath).isDirectory()) {
190 readDir(filePath);
191 } else {
192 coreExtensionsJson[normalizedPath] = fs.readFileSync(
193 filePath,
194 "utf8"
195 );
196 }
197 }
198 }
199
200 readDir("./dist/core-extensions");
201
202 esbuildConfig.banner = {
203 js: `window._moonlight_coreExtensionsStr = ${JSON.stringify(
204 JSON.stringify(coreExtensionsJson)
205 )};`
206 };
207 }
208
209 if (watch) {
210 const ctx = await esbuild.context(esbuildConfig);
211 await ctx.watch();
212 } else {
213 await esbuild.build(esbuildConfig);
214 }
215}
216
217async function buildExt(ext, side, copyManifest, fileExt) {
218 const outdir = path.join("./dist", "core-extensions", ext);
219 if (!fs.existsSync(outdir)) {
220 fs.mkdirSync(outdir, { recursive: true });
221 }
222
223 const entryPoints = [
224 `packages/core-extensions/src/${ext}/${side}.${fileExt}`
225 ];
226
227 const wpModulesDir = `packages/core-extensions/src/${ext}/webpackModules`;
228 if (fs.existsSync(wpModulesDir) && side === "index") {
229 const wpModules = fs.opendirSync(wpModulesDir);
230 for await (const wpModule of wpModules) {
231 if (wpModule.isFile()) {
232 entryPoints.push(
233 `packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}`
234 );
235 } else {
236 for (const fileExt of ["ts", "tsx"]) {
237 const path = `packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}/index.${fileExt}`;
238 if (fs.existsSync(path)) {
239 entryPoints.push({
240 in: path,
241 out: `webpackModules/${wpModule.name}`
242 });
243 }
244 }
245 }
246 }
247 }
248
249 const wpImportPlugin = {
250 name: "webpackImports",
251 setup(build) {
252 build.onResolve({ filter: /^@moonlight-mod\/wp\// }, (args) => {
253 const wpModule = args.path.replace(/^@moonlight-mod\/wp\//, "");
254 return {
255 path: wpModule,
256 external: true
257 };
258 });
259 }
260 };
261
262 const esbuildConfig = {
263 entryPoints,
264 outdir,
265
266 format: "iife",
267 globalName: "module.exports",
268 platform: "node",
269
270 treeShaking: true,
271 bundle: true,
272 sourcemap: prod ? false : "inline",
273
274 external,
275
276 logOverride: {
277 "commonjs-variable-in-esm": "verbose"
278 },
279 logLevel: "silent",
280 plugins: [
281 ...(copyManifest
282 ? [
283 copyStaticFiles({
284 src: `./packages/core-extensions/src/${ext}/manifest.json`,
285 dest: `./dist/core-extensions/${ext}/manifest.json`
286 })
287 ]
288 : []),
289 wpImportPlugin,
290 deduplicatedLogging,
291 taggedBuildLog(`ext/${ext}`)
292 ]
293 };
294
295 if (watch) {
296 const ctx = await esbuild.context(esbuildConfig);
297 await ctx.watch();
298 } else {
299 await esbuild.build(esbuildConfig);
300 }
301}
302
303const promises = [];
304
305if (browser) {
306 build("browser", "packages/browser/src/index.ts");
307} else {
308 for (const [name, entry] of Object.entries(config)) {
309 promises.push(build(name, entry));
310 }
311
312 const coreExtensions = fs.readdirSync("./packages/core-extensions/src");
313 for (const ext of coreExtensions) {
314 let copiedManifest = false;
315
316 for (const fileExt of ["ts", "tsx"]) {
317 for (const type of ["index", "node", "host"]) {
318 if (
319 fs.existsSync(
320 `./packages/core-extensions/src/${ext}/${type}.${fileExt}`
321 )
322 ) {
323 promises.push(buildExt(ext, type, !copiedManifest, fileExt));
324 copiedManifest = true;
325 }
326 }
327 }
328 }
329}
330
331await Promise.all(promises);