// deno task new-mod ["Display Name"] // copies templates/mod into mods/, with the template's placeholder id and name replaced import { NAMESPACE_PATTERN, RESERVED_NAMESPACES } from "$/common/mod_data.ts"; const TEMPLATE = "templates/mod"; const PLACEHOLDER_ID = "example_mod"; const PLACEHOLDER_NAME = "Example Mod"; const TEXT_FILES = /\.(json|md|ts|js)$/; export function create_mod(id: string, name: string, mods_dir = "mods") { if (!NAMESPACE_PATTERN.test(id)) { throw new Error(`"${id}" isn't a valid mod id: use 1-32 characters of a-z, 0-9 and _`); } if (RESERVED_NAMESPACES.includes(id)) { throw new Error(`"${id}" is reserved`); } const target = `${mods_dir}/${id}`; try { Deno.statSync(target); throw new Error(`${target} already exists`); } catch (e) { if (!(e instanceof Deno.errors.NotFound)) throw e; } copy_dir(TEMPLATE, target, (text) => text.replaceAll(PLACEHOLDER_ID, id).replaceAll(PLACEHOLDER_NAME, name)); return target; } function copy_dir(from: string, to: string, transform: (text: string) => string) { Deno.mkdirSync(to, { recursive: true }); for (const entry of Deno.readDirSync(from)) { const source = `${from}/${entry.name}`; const destination = `${to}/${entry.name}`; if (entry.isDirectory) { copy_dir(source, destination, transform); } else if (TEXT_FILES.test(entry.name)) { Deno.writeTextFileSync(destination, transform(Deno.readTextFileSync(source))); } else { Deno.copyFileSync(source, destination); } } } if (import.meta.main) { const [id, name] = Deno.args; if (!id) { console.error('Usage: deno task new-mod ["Display Name"]'); Deno.exit(1); } const display_name = name ?? id.split("_").map((word) => word[0].toUpperCase() + word.slice(1)).join(" "); try { const path = create_mod(id, display_name); console.log(`Created ${path}. Edit its manifest.json, then run deno task check-mods.`); console.log("Note: the game can't load mods yet, see the implementation plan in MODS.md."); } catch (e) { console.error((e as Error).message); Deno.exit(1); } }