102 lines
3.9 KiB
TypeScript
102 lines
3.9 KiB
TypeScript
// deno task pack-mod mods/copper_tools [copper_tools.bmod]
|
|
// builds a mod folder into a .bmod: its data merged into data.json, each script bundled into one module, and its
|
|
// textures and credits. deno task build does this for every mod in mods/, see "Mod files" in MODS.md
|
|
import { basename, dirname, resolve } from "@std/path";
|
|
import { type Bmod, BMOD_EXTENSION, SCRIPT_SIDES, type ScriptSide, write_bmod } from "$/common/bmod.ts";
|
|
import type { ManifestJson } from "$/common/mod_data.ts";
|
|
import { load_and_check, type LoadedMod } from "./check_mods.ts";
|
|
|
|
// what each script is bundled for: the server runs in deno, clients in browsers, worldgen in both
|
|
const PLATFORMS: Record<ScriptSide, "deno" | "browser"> = { server: "deno", client: "browser", worldgen: "browser" };
|
|
|
|
// a mod that passed check-mods, as .bmod bytes
|
|
export async function pack_mod(mod: LoadedMod): Promise<Uint8Array<ArrayBuffer>> {
|
|
const manifest = mod.manifest as unknown as ManifestJson;
|
|
const scripts: Bmod["scripts"] = {};
|
|
for (const side of SCRIPT_SIDES) {
|
|
const entry = manifest.scripts?.[side];
|
|
if (entry) scripts[side] = await bundle_script(mod.dir, `${mod.dir}/${entry}`, PLATFORMS[side]);
|
|
}
|
|
|
|
const textures = new Map<string, Uint8Array>();
|
|
for (const [id, path] of [...mod.texture_files].sort(([a], [b]) => a.localeCompare(b))) {
|
|
textures.set(id, Deno.readFileSync(path));
|
|
}
|
|
|
|
return write_bmod({
|
|
manifest,
|
|
data: {
|
|
blocks: mod.blocks.map((b) => b.json),
|
|
models: mod.models.map((m) => m.json),
|
|
items: mod.items.map((i) => i.json),
|
|
recipes: mod.recipes.map((r) => r.json),
|
|
ores: mod.ores.map((o) => o.json),
|
|
},
|
|
scripts,
|
|
textures,
|
|
credits: manifest.credits ? Deno.readTextFileSync(`${mod.dir}/${manifest.credits}`) : undefined,
|
|
});
|
|
}
|
|
|
|
// a mod is a deno project: with its own deno.json its scripts are bundled with it (its imports, npm packages and
|
|
// so on), otherwise with the deno.json of the folder the build runs in
|
|
async function bundle_script(mod_dir: string, entry: string, platform: "deno" | "browser"): Promise<string> {
|
|
const config = ["deno.json", "deno.jsonc"].map((name) => `${mod_dir}/${name}`).find(exists);
|
|
if (!config) {
|
|
const result = await Deno.bundle({ entrypoints: [entry], platform, write: false, minify: false });
|
|
if (!result.success || !result.outputFiles?.length) {
|
|
throw new Error(`Couldn't bundle ${entry}:\n${result.errors.map((e) => e.text).join("\n")}`);
|
|
}
|
|
return result.outputFiles[0].text();
|
|
}
|
|
|
|
const output = await Deno.makeTempFile({ suffix: ".js" });
|
|
try {
|
|
const result = await new Deno.Command(Deno.execPath(), {
|
|
args: ["bundle", "--config", config, "--platform", platform, "--output", output, entry],
|
|
stdout: "piped",
|
|
stderr: "piped",
|
|
env: { NO_COLOR: "1" },
|
|
}).output();
|
|
if (!result.success) {
|
|
throw new Error(`Couldn't bundle ${entry}:\n${new TextDecoder().decode(result.stderr).trim()}`);
|
|
}
|
|
return Deno.readTextFileSync(output);
|
|
} finally {
|
|
Deno.removeSync(output);
|
|
}
|
|
}
|
|
|
|
function exists(path: string) {
|
|
try {
|
|
Deno.statSync(path);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (import.meta.main) {
|
|
const [dir, out] = Deno.args;
|
|
if (!dir) {
|
|
console.error("usage: deno task pack-mod <mod folder> [output.bmod]");
|
|
Deno.exit(1);
|
|
}
|
|
// its siblings are checked too, so references to mods it depends on can be checked
|
|
const folder = resolve(dir);
|
|
const mod = load_and_check(dirname(folder)).find((m) => resolve(m.dir) === folder);
|
|
if (!mod) {
|
|
console.error(`${dir} isn't a mod folder`);
|
|
Deno.exit(1);
|
|
}
|
|
if (mod.report.errors.length > 0) {
|
|
console.error(`${mod.id} has errors (deno task check-mods for details):`);
|
|
for (const error of mod.report.errors) console.error(` ${error}`);
|
|
Deno.exit(1);
|
|
}
|
|
const file = out ?? `${basename(folder)}${BMOD_EXTENSION}`;
|
|
const bytes = await pack_mod(mod);
|
|
Deno.writeFileSync(file, bytes);
|
|
console.log(`Packed ${mod.id} into ${file} (${(bytes.length / 1024).toFixed(1)} KB)`);
|
|
}
|