Lightcord/scripts/build.js

506 lines
14 KiB
JavaScript

const child_process = require("child_process");
const path = require("path");
const terser = require("terser");
const util = require("util");
const production = true;
const includeSourcesMaps = true;
let fs = require("fs");
exports.default = async function beforeBuild(context) {
await main();
return true;
};
const PROJECT_DIR = path.resolve(__dirname, "..");
console.log = (...args) => {
process.stdout.write(
Buffer.from(
util.formatWithOptions({ colors: true }, ...args) + "\n",
"binary"
).toString("utf8")
);
};
console.info = (...args) => {
console.log(`\x1b[34m[INFO]\x1b[0m`, ...args);
};
let commit = child_process
.execSync("git rev-parse HEAD")
.toString()
.split("\n")[0]
.trim();
console.info(`Obtained commit ${commit} for the build`);
async function processNextDir(
folder,
folders,
predicate,
compile,
ignoreModules
) {
if (typeof ignoreModules === "undefined") ignoreModules = false;
let files = fs.readdirSync(folder, { withFileTypes: true });
for (let file of files) {
if (file.isFile()) {
let isMinified =
file.name.endsWith(".min.js") || file.name.endsWith(".min.css");
let filepath = path.join(folder, file.name);
let type = file.name.split(".").pop().toLowerCase();
if (type === file.name) type = "";
if (["ts", "md", "gitignore", "map"].includes(type)) {
console.warn(
`\x1b[33mIgnored file ${path.relative(
folders.startDir,
filepath
)} because of type ${type}\x1b[0m`
);
continue;
}
if (["tsconfig.json", "webpack.config.js"].includes(file.name)) {
console.warn(
`\x1b[33mIgnored file ${path.relative(
folders.startDir,
filepath
)} because of name ${file.name}\x1b[0m`
);
continue;
}
if (folders.exclude && folders.exclude.test(filepath)) {
console.warn(
`\x1b[33mIgnored file ${path.relative(
folders.startDir,
filepath
)} because regex\x1b[0m`
);
continue;
}
let hasMinifiedVersion =
(type === "js" || type === "css") &&
!isMinified &&
files.find((f) => {
return (
f.name ===
file.name.split(".").slice(0, -1).join(".") + ".min." + type
);
});
if (hasMinifiedVersion) {
console.warn(
`\x1b[33mIgnored file ${path.relative(
folders.startDir,
filepath
)} because it has a minified version.\x1b[0m`
);
continue;
}
if (
!isMinified &&
predicate(filepath) &&
filepath.split(/[\\/]+/).reverse()[1] !== "js"
) {
await compile(
filepath,
path.join(filepath.replace(folders.startDir, folders.newDir)),
".."
);
} else {
if (["js", "css"].includes(type)) {
if (!includeSourcesMaps) {
console.log(
`We don't include sourcemap for this build. Skipping ${file.name}.`
);
return await fs.promises.copyFile(
filepath,
filepath.replace(folders.startDir, folders.newDir)
);
}
let fileContent = await fs.promises.readFile(filepath, "utf8");
let sourceMap = fileContent.split(/[\n\r]+/g).pop();
if (!sourceMap || !sourceMap.startsWith("//# sourceMappingURL=")) {
console.log(`This file doesn't have sourcemap. ${file.name}.`);
await fs.promises.copyFile(
filepath,
filepath.replace(folders.startDir, folders.newDir)
);
continue;
}
let sourceMapContent;
if (sourceMap.slice(21).startsWith("data:")) {
console.log(
`Extracting sourcemap from data uri. From file ${file.name}.`
);
sourceMapContent = Buffer.from(
sourceMap.split("=").slice(1).join("="),
"base64"
).toString("utf-8");
} else {
console.log(`Extracting sourcemap from file ${file.name}.map.`);
await fs.promises.copyFile(
filepath,
filepath.replace(folders.startDir, folders.newDir)
);
sourceMapContent = await fs.promises.readFile(
path.join(folder, sourceMap.slice(21)),
"utf8"
);
}
sourceMapContent = JSON.parse(sourceMapContent);
sourceMapContent.sourcesContent = [];
let sourceMapPath = filepath + ".map";
fileContent = fileContent
// source map
.replace(
sourceMap,
"//# sourceMappingURL=" +
filepath.split(/[\\\/]+/g).pop() +
".map"
);
await fs.promises.writeFile(
filepath.replace(folders.startDir, folders.newDir),
fileContent
);
await fs.promises.writeFile(
filepath.replace(folders.startDir, folders.newDir) + ".map",
JSON.stringify(sourceMapContent)
);
} else {
await fs.promises.copyFile(
filepath,
filepath.replace(folders.startDir, folders.newDir)
);
}
}
} else if (file.isDirectory()) {
if (ignoreModules && file.name === "node_modules") continue;
if (folders.exclude && folders.exclude.test(path.join(folder, file.name)))
continue;
await fs.promises.mkdir(
path.join(folder, file.name).replace(folders.startDir, folders.newDir),
{ recursive: true }
);
await processNextDir(
path.join(folder, file.name),
...Array.from(arguments).slice(1)
);
}
}
}
async function main() {
let startTimestamp = Date.now();
console.info("Starting build");
console.info("Reseting existent directory...");
try {
await fs.promises.rm("./distApp", { recursive: true });
} catch (error) {
console.error(error);
}
await fs.promises.mkdir(PROJECT_DIR + "/distApp/dist", { recursive: true });
console.info("Executing command `yarn compile`");
child_process.execSync("yarn compile", {
encoding: "binary",
stdio: "inherit",
});
let startDir = path.join(PROJECT_DIR, "./dist");
let newDir = path.join(PROJECT_DIR, "./distApp/dist");
console.info("No error detected. Copying files from " + startDir + ".");
await fs.promises.mkdir(startDir, { recursive: true });
await processNextDir(
startDir,
{
startDir,
newDir,
},
(filepath) => filepath.endsWith(".js"),
async (filepath, newpath) => {
console.info(`Minifying ${filepath} to ${newpath}`);
if (filepath.endsWith("git.js")) {
await fs.promises.writeFile(
newpath,
(
await terser.minify(
fs
.readFileSync(filepath, "utf8")
.replace(/"{commit}"/g, `"${commit}"`)
)
).code,
"utf8"
);
} else {
await fs.promises.writeFile(
newpath,
(
await terser.minify(await fs.promises.readFile(filepath, "utf8"))
).code,
"utf8"
);
}
},
true
).then(() => {
console.info(`Copied files and minified them from ${startDir}.`);
});
await processNextDir(
path.join(PROJECT_DIR, "modules"),
{
startDir: path.join(PROJECT_DIR, "modules"),
newDir: path.join(PROJECT_DIR, "distApp", "modules"),
exclude: /discord_spellcheck/g,
},
(filepath) => filepath.endsWith(".js"),
async (filepath, newpath) => {
console.info(`Minifying ${filepath} to ${newpath}`);
await fs.promises.writeFile(
newpath,
(await terser.minify(await fs.promises.readFile(filepath, "utf8"))).code,
"utf8"
);
},
true
).then(() => {
console.info(
`Copied files and minified them from ${path.join(
PROJECT_DIR,
"modules"
)}.`
);
});
await Promise.all(
(
await fs.promises.readdir(path.join(PROJECT_DIR, "distApp", "modules"))
).map(async (mdl) => {
let dir = path.join(PROJECT_DIR, "distApp", "modules", mdl);
if (!fs.existsSync(path.join(dir, "package.json"))) {
if (mdl === "discord_desktop_core") {
dir = path.join(dir, "core");
} else {
return;
}
}
console.info(`Installing modules for ${mdl}`);
child_process.execSync("yarn --production", {
encoding: "binary",
cwd: dir,
stdio: "inherit",
});
})
);
await fs.promises.mkdir(
path.join(PROJECT_DIR, "distApp", "modules", "discord_spellcheck"),
{ recursive: true }
);
await processNextDir(
path.join(PROJECT_DIR, "modules", "discord_spellcheck"),
{
startDir: path.join(PROJECT_DIR, "modules", "discord_spellcheck"),
newDir: path.join(
PROJECT_DIR,
"distApp",
"modules",
"discord_spellcheck"
),
},
(filepath) => filepath.endsWith(".js"),
async (filepath, newpath) => {
console.info(`Minifying ${filepath} to ${newpath}`);
await fs.promises.writeFile(
newpath,
(await terser.minify(await fs.promises.readFile(filepath, "utf8"))).code,
"utf8"
);
},
false
).then(() => {
console.info(
`Copied files and minified them from ${path.join(
PROJECT_DIR,
"modules"
)}.`
);
});
await processNextDir(
path.join(PROJECT_DIR, "LightcordApi"),
{
startDir: path.join(PROJECT_DIR, "LightcordApi"),
newDir: path.join(PROJECT_DIR, "distApp", "LightcordApi"),
exclude: /(src|webpack\.config\.js|tsconfig\.json|dist|docs)/g,
},
(filepath) =>
filepath.endsWith(".js") &&
(!production ? !filepath.includes("node_modules") : true),
async (filepath, newpath) => {
await fs.promises.copyFile(filepath, newpath);
},
true
).then(() => {
console.info(
`Copied files and minified them from ${path.join(
PROJECT_DIR,
"LightcordApi"
)}.`
);
});
child_process.execSync("yarn --production", {
encoding: "binary",
cwd: path.join(PROJECT_DIR, "distApp", "LightcordApi"),
stdio: "inherit",
});
function processDJS(dir) {
fs.mkdirSync(path.join(PROJECT_DIR, "distApp", "DiscordJS", dir), {
recursive: true,
});
return processNextDir(
path.join(PROJECT_DIR, "DiscordJS", dir),
{
startDir: path.join(PROJECT_DIR, "DiscordJS", dir),
newDir: path.join(PROJECT_DIR, "distApp", "DiscordJS", dir),
exclude: /node_modules/g,
},
(filepath) => filepath.endsWith(".js"),
async (filepath, newpath) => {
console.info(`Minifying ${filepath} to ${newpath}`);
await fs.promises.writeFile(
newpath,
(await terser.minify(await fs.promises.readFile(filepath, "utf8"))).code,
"utf8"
);
}
).then(() => {
console.info(
`Copied files and minified them from ${path.join(
PROJECT_DIR,
"DiscordJS",
dir
)}.`
);
});
}
async function copyFileDJS(file) {
await fs.promises.writeFile(
path.join(PROJECT_DIR, "distApp", "DiscordJS", file),
await fs.promises.readFile(path.join(PROJECT_DIR, "DiscordJS", file))
);
}
await processDJS("dist");
await copyFileDJS("package.json");
child_process.execSync("yarn --production", {
encoding: "binary",
cwd: path.join(PROJECT_DIR, "distApp", "DiscordJS"),
stdio: "inherit",
});
fs.mkdirSync(path.join(PROJECT_DIR, "distApp", "BetterDiscordApp", "dist"), {
recursive: true,
});
const BDPackageJSON = require("../BetterDiscordApp/package.json");
fs.writeFileSync(
path.join(PROJECT_DIR, "distApp", "BetterDiscordApp", "package.json"),
JSON.stringify(BDPackageJSON),
"utf8"
);
const files = ["index.min.js", "style.min.css"];
files.forEach((e) => {
files.push(e + ".map");
});
files.forEach((e) => {
const pth = path.join(PROJECT_DIR, "BetterDiscordApp", "dist", e);
if (!fs.existsSync(pth))
return console.error(
`\x1b[31mFile ${pth} from betterdiscord does not exist.\x1b[0m`
);
if (e.endsWith(".map")) {
const data = JSON.parse(fs.readFileSync(pth, "utf8"));
data.sourcesContent = [];
fs.writeFileSync(
path.join(PROJECT_DIR, "distApp", "BetterDiscordApp", "dist", e),
JSON.stringify(data)
);
} else {
fs.copyFileSync(
pth,
path.join(PROJECT_DIR, "distApp", "BetterDiscordApp", "dist", e)
);
}
});
await fs.promises.mkdir(
path.join(PROJECT_DIR, "distApp", "splash", "videos"),
{ recursive: true }
);
await processNextDir(
path.join(PROJECT_DIR, "splash"),
{
startDir: path.join(PROJECT_DIR, "splash"),
newDir: path.join(PROJECT_DIR, "distApp", "splash"),
exclude: /node_modules/g,
},
(filepath) => {
if (filepath.endsWith(".js")) return true;
return false;
},
async (filepath, newpath) => {
console.info(`Minifying ${filepath} to ${newpath}`);
await fs.promises.writeFile(
newpath,
(await terser.minify(await fs.promises.readFile(filepath, "utf8"))).code,
"utf8"
);
}
).then(() => {
console.info(
`Copied files and minified them from ${path.join(PROJECT_DIR, "splash")}.`
);
});
fs.writeFileSync(
path.join(PROJECT_DIR, "distApp", "LICENSE"),
fs.readFileSync(path.join(PROJECT_DIR, "LICENSE"))
);
let packageJSON = require("../package.json");
packageJSON.scripts["build:electron_linux"] = packageJSON.scripts[
"build:electron_linux"
].replace("./distApp", ".");
packageJSON.scripts["build:electron_win"] = packageJSON.scripts[
"build:electron_win"
].replace("./distApp", ".");
packageJSON.scripts["build:electron_mac"] = packageJSON.scripts[
"build:electron_mac"
].replace("./distApp", ".");
fs.writeFileSync(
path.join(PROJECT_DIR, "distApp", "package.json"),
JSON.stringify(packageJSON),
"utf8"
);
console.info(
`Installing ${Object.keys(packageJSON.dependencies).length} packages...`
);
child_process.execSync("yarn --production", {
encoding: "binary",
cwd: path.join(PROJECT_DIR, "distApp"),
stdio: "inherit",
});
console.info("Build took " + (Date.now() - startTimestamp) + "ms.");
}
main().catch((err) => {
console.error(err);
process.exit(1);
});