Files
factorio-clusterio/packages/lib/build_mod.js
Hornwitser 5dcaca4d0b Migrate to ECMAScript Modules
Migrate the usage of CommonJS specific code and imports to ESM.  Roughly
summarised this:
- Changes all require() calls to import statement or expressions.
- Changes usage of module.exports to export statements.
- Replaces require.resolve with import.meta.resolve.
- Replaces conditional imports with conditional subpath imports.
- Replaces module === require.main with import.meta.main.
- Adds the full filename with extention to all relative imports.
- Changes all usage of __dirname and __filename to their import.meta
  equivalents.
- Sets "type": "module" in package.json files.
- Removes usage of "use strict".
- Adds .ts an an alias of .js for the webpack build.

Due to differences in how require and import resolves files the plugin
entrypoints will have to include the filename and extension after this
migration in order to work.
2026-09-11 22:43:57 +02:00

155 lines
4.5 KiB
JavaScript

/* eslint-disable no-console */
import fs from "node:fs/promises";
import JSZip from "jszip";
import path from "node:path";
import stream from "node:stream";
import util from "node:util";
import yargs from "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"));
}
}
}
export 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(process.argv.slice(2))
.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()
.parse()
;
await build(args);
}
if (import.meta.main) {
main().catch(err => { console.log(err); });
}