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");
16
17const external = [
18 "electron",
19 "fs",
20 "path",
21 "module",
22 "events",
23 "original-fs", // wtf asar?
24 "discord", // mappings
25
26 // Silence an esbuild warning
27 "./node-preload.js"
28];
29
30let lastMessages = new Set();
31/** @type {import("esbuild").Plugin} */
32const deduplicatedLogging = {
33 name: "deduplicated-logging",
34 setup(build) {
35 build.onStart(() => {
36 lastMessages.clear();
37 });
38
39 build.onEnd(async (result) => {
40 const formatted = await Promise.all([
41 esbuild.formatMessages(result.warnings, {
42 kind: "warning",
43 color: true
44 }),
45 esbuild.formatMessages(result.errors, { kind: "error", color: true })
46 ]).then((a) => a.flat());
47
48 // console.log(formatted);
49 for (const message of formatted) {
50 if (lastMessages.has(message)) continue;
51 lastMessages.add(message);
52 console.log(message.trim());
53 }
54 });
55 }
56};
57
58const timeFormatter = new Intl.DateTimeFormat(undefined, {
59 hour: "numeric",
60 minute: "numeric",
61 second: "numeric",
62 hour12: false
63});
64/** @type {import("esbuild").Plugin} */
65const taggedBuildLog = (tag) => ({
66 name: "build-log",
67 setup(build) {
68 build.onEnd((result) => {
69 console.log(
70 `[${timeFormatter.format(new Date())}] [${tag}] build finished`
71 );
72 });
73 }
74});
75
76async function build(name, entry) {
77 const outfile = path.join("./dist", name + ".js");
78
79 const dropLabels = [];
80 if (name !== "injector") dropLabels.push("injector");
81 if (name !== "node-preload") dropLabels.push("nodePreload");
82 if (name !== "web-preload") dropLabels.push("webPreload");
83
84 const define = {
85 MOONLIGHT_ENV: `"${name}"`,
86 MOONLIGHT_PROD: prod.toString()
87 };
88
89 for (const iterName of Object.keys(config)) {
90 const snake = iterName.replace(/-/g, "_").toUpperCase();
91 define[`MOONLIGHT_${snake}`] = (name === iterName).toString();
92 }
93
94 const nodeDependencies = ["glob"];
95 const ignoredExternal = name === "web-preload" ? nodeDependencies : [];
96
97 /** @type {import("esbuild").BuildOptions} */
98 const esbuildConfig = {
99 entryPoints: [entry],
100 outfile,
101
102 format: "cjs",
103 platform: name === "web-preload" ? "browser" : "node",
104
105 treeShaking: true,
106 bundle: true,
107 minify: prod,
108 sourcemap: "inline",
109
110 external: [...ignoredExternal, ...external],
111
112 define,
113 dropLabels,
114
115 logLevel: "silent",
116 plugins: [deduplicatedLogging, taggedBuildLog(name)]
117 };
118
119 if (watch) {
120 const ctx = await esbuild.context(esbuildConfig);
121 await ctx.watch();
122 } else {
123 await esbuild.build(esbuildConfig);
124 }
125}
126
127async function buildExt(ext, side, copyManifest, fileExt) {
128 const outdir = path.join("./dist", "core-extensions", ext);
129 if (!fs.existsSync(outdir)) {
130 fs.mkdirSync(outdir, { recursive: true });
131 }
132
133 const entryPoints = [
134 `packages/core-extensions/src/${ext}/${side}.${fileExt}`
135 ];
136
137 const wpModulesDir = `packages/core-extensions/src/${ext}/webpackModules`;
138 if (fs.existsSync(wpModulesDir) && side === "index") {
139 const wpModules = fs.opendirSync(wpModulesDir);
140 for await (const wpModule of wpModules) {
141 if (wpModule.isFile()) {
142 entryPoints.push(
143 `packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}`
144 );
145 } else {
146 for (const fileExt of ["ts", "tsx"]) {
147 const path = `packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}/index.${fileExt}`;
148 if (fs.existsSync(path)) {
149 entryPoints.push({
150 in: path,
151 out: `webpackModules/${wpModule.name}`
152 });
153 }
154 }
155 }
156 }
157 }
158
159 const wpImportPlugin = {
160 name: "webpackImports",
161 setup(build) {
162 build.onResolve({ filter: /^@moonlight-mod\/wp\// }, (args) => {
163 const wpModule = args.path.replace(/^@moonlight-mod\/wp\//, "");
164 return {
165 path: wpModule,
166 external: true
167 };
168 });
169 }
170 };
171
172 const esbuildConfig = {
173 entryPoints,
174 outdir,
175
176 format: "cjs",
177 platform: "node",
178
179 treeShaking: true,
180 bundle: true,
181 sourcemap: prod ? false : "inline",
182
183 external,
184
185 logOverride: {
186 "commonjs-variable-in-esm": "verbose"
187 },
188 logLevel: "silent",
189 plugins: [
190 ...(copyManifest
191 ? [
192 copyStaticFiles({
193 src: `./packages/core-extensions/src/${ext}/manifest.json`,
194 dest: `./dist/core-extensions/${ext}/manifest.json`
195 })
196 ]
197 : []),
198 wpImportPlugin,
199 deduplicatedLogging,
200 taggedBuildLog(`ext/${ext}`)
201 ]
202 };
203
204 if (watch) {
205 const ctx = await esbuild.context(esbuildConfig);
206 await ctx.watch();
207 } else {
208 await esbuild.build(esbuildConfig);
209 }
210}
211
212const promises = [];
213
214for (const [name, entry] of Object.entries(config)) {
215 promises.push(build(name, entry));
216}
217
218const coreExtensions = fs.readdirSync("./packages/core-extensions/src");
219for (const ext of coreExtensions) {
220 let copiedManifest = false;
221
222 for (const fileExt of ["ts", "tsx"]) {
223 for (const type of ["index", "node", "host"]) {
224 if (
225 fs.existsSync(
226 `./packages/core-extensions/src/${ext}/${type}.${fileExt}`
227 )
228 ) {
229 promises.push(buildExt(ext, type, !copiedManifest, fileExt));
230 copiedManifest = true;
231 }
232 }
233 }
234}
235
236await Promise.all(promises);