Files
factorio-clusterio/packages/lib/build_mod.js
T
Hornwitser 2e48b728dd Remove fs-extra and klaw (#882)
These packages are obsoleted by the promises API to the built-in Node fs
module.  The fs-extra package is also causing problems with broken APIs
that don't behave the same as Node's fs module, and throwing errors
without code properties on them.
2026-04-09 22:53:13 +02:00

160 lines
4.5 KiB
JavaScript

/* eslint-disable no-console */
"use strict";
const fs = require("node:fs/promises");
const JSZip = require("jszip");
const path = require("path");
const stream = require("stream");
const util = require("util");
const yargs = require("yargs");
const finished = util.promisify(stream.finished);
async function buildMod(args, info, modName) {
if (args.build) {
await fs.mkdir(args.outputDir, { recursive: true });
modName = args.modName ?? `${info.name}_${info.version}`;
if (args.pack) {
let zip = new JSZip();
for await (const dirent of await fs.opendir(args.sourceDir, { recursive: true })) {
if (dirent.isFile()) {
const itemPath = path.join(dirent.parentPath, dirent.name);
// On Windows the path created uses backslashes as the directory sepparator
// but the zip file needs to use forward slashes. We can't use the posix
// version of relative here as it doesn't work with Windows style paths.
let basePath = path.relative(args.sourceDir, itemPath).replace(/\\/g, "/");
// eslint-disable-next-line max-depth
if (basePath === "info.json") {
// The info.json file is overridden later.
continue;
}
zip.file(path.posix.join(modName, basePath), (await fs.open(itemPath)).createReadStream());
}
}
for (let [fileName, pathParts] of Object.entries(info.additional_files || {})) {
let filePath = path.join(args.sourceDir, ...pathParts);
let fileStream;
try {
fileStream = (await fs.open(filePath)).createReadStream();
} catch (err) {
throw new Error(`Error reading additional file ${filePath}`, { cause: err });
}
zip.file(path.posix.join(modName, fileName), fileStream);
}
delete info.additional_files;
zip.file(path.posix.join(modName, "info.json"), JSON.stringify(info, null, "\t"));
let modPath = path.join(args.outputDir, `${modName}.zip`);
console.log(`Writing ${modPath}`);
let writeStream = zip.generateNodeStream().pipe(
(await fs.open(modPath, "w")).createWriteStream()
);
await finished(writeStream);
} else {
let modDir = path.join(args.outputDir, modName);
console.log(`Removing potentially existing build ${modDir}`);
await fs.rm(modDir, { force: true, recursive: true, maxRetries: 10 });
console.log(`Building ${modDir}`);
await fs.cp(args.sourceDir, modDir, {
errorOnExists: true,
mode: fs.constants.COPYFILE_FICLONE,
recursive: true,
});
for (let [fileName, pathParts] of Object.entries(info.additional_files) || []) {
let filePath = path.join(...pathParts);
await fs.copyFile(
filePath,
path.join(modDir, fileName),
fs.constants.COPYFILE_EXCL | fs.constants.COPYFILE_FICLONE,
);
}
delete info.additional_files;
await fs.writeFile(path.join(modDir, "info.json"), JSON.stringify(info, null, "\t"));
}
}
}
async function build(args) {
let info = JSON.parse(await fs.readFile(path.join(args.sourceDir, "info.json")));
if (args.factorioVersion) {
info.factorio_version = args.factorioVersion;
}
if (args.dependencies) {
info.dependencies = args.dependencies;
}
if (args.clean) {
let splitter = /^(.*)_(\d+\.\d+\.\d+)(\.zip)?$/;
for (let entry of await fs.readdir(args.outputDir)) {
let match = splitter.exec(entry);
if (match) {
let name = match[1];
if (name === info.name) {
let modPath = path.join(args.outputDir, entry);
console.log(`Removing ${modPath}`);
await fs.rm(modPath, { force: true, recursive: true, maxRetries: 10 });
}
}
}
}
if (info.variants) {
for (let variantOverrides of info.variants) {
let variantInfo = {
...info,
...variantOverrides,
};
delete variantInfo.variants;
await buildMod(args, variantInfo);
}
} else {
await buildMod(args, info);
}
}
async function main() {
const args = yargs
.scriptName("build")
.options({
"clean": { describe: "Remove previous builds", type: "boolean", default: false },
"build": { describe: "Build mod(s)", type: "boolean", default: true },
"pack": { describe: "Pack into zip file", type: "boolean", default: true },
"factorio-version": { describe: "Override factorio_version", type: "string" },
"source-dir": {
describe: "Path to mod source files",
type: "string",
nargs: 1,
normalize: true,
demandOption: true,
},
"output-dir": {
describe: "Path to output built mod(s)",
type: "string",
nargs: 1,
normalize: true,
default: "dist/factorio",
},
})
.strict()
.argv
;
await build(args);
}
if (module === require.main) {
main().catch(err => { console.log(err); });
}
module.exports = {
build,
};