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