import { spawn } from "node:child_process"; import path from "node:path"; import { clientRootForRecipe, exists, fixturesRoot, loadRecipe, projectRoot, readJson, recipesRoot, workRoot, } from "./lib.mjs"; async function runExporter(argumentsList) { const executable = path.resolve(projectRoot, "../wow335a/dungeon-export-work/tools/wow.export-0.2.19/wow.export.exe"); if (!await exists(executable)) throw new Error("The patched portable wow.export executable was not found."); await new Promise((resolve, reject) => { const child = spawn(executable, ["--disable-auto-update", ...argumentsList], { cwd: path.dirname(executable), stdio: "inherit", windowsHide: true, }); child.once("error", reject); child.once("exit", (code) => code === 0 ? resolve() : reject(new Error(`wow.export exited with code ${code}.`))); }); } async function checkedResult(slug, output) { const resultFile = path.join(output, "automation-result.json"); if (!await exists(resultFile)) return { status: "blocked", dungeonId: slug, blockers: ["wow.export produced no automation-result.json."] }; const result = await readJson(resultFile); if (!result.ok) return { status: "blocked", dungeonId: slug, blockers: result.missingDependencies?.length ? result.missingDependencies.map((dependency) => `Missing ${dependency.type} dependency ${dependency.path}.`) : [result.error ?? "wow.export reported an unknown extraction failure."], }; return null; } export async function exportDungeonSource(slug) { const recipe = await loadRecipe(slug); const clientRoot = clientRootForRecipe(recipe); const output = path.join(workRoot, slug, "source-export"); await runExporter([ "--export-dungeon", "--recipe", path.join(recipesRoot, `${slug}.json`), "--client-root", clientRoot, "--output", output, ]); const blocked = await checkedResult(slug, output); if (blocked) return blocked; return { status: "green", dungeonId: slug, output: path.relative(projectRoot, output).replace(/\\/g, "/") }; } export async function exportClientDiscoveryTables(slug) { const recipe = await loadRecipe(slug); const clientRoot = clientRootForRecipe(recipe); const output = path.join(workRoot, slug, "client-dbc"); await runExporter([ "--discover-dungeon", "--recipe", path.join(recipesRoot, `${slug}.json`), "--client-root", clientRoot, "--output", output, ]); return output; } export async function exportCreatureSource(slug) { const recipe = await loadRecipe(slug); const clientRoot = clientRootForRecipe(recipe); let selected = recipe.creatures?.length ? path.join(recipesRoot, `${slug}.json`) : null; for (const candidate of [ path.join(workRoot, slug, "creature-export.recipe.json"), path.join(fixturesRoot, slug, "creature-export.recipe.json"), ]) { if (await exists(candidate)) { selected = candidate; break; } } if (!selected) throw new Error(`${slug}: reviewed creature-export.recipe.json is missing.`); const selectedRecipe = await readJson(selected); const unresolved = (selectedRecipe.creatures ?? []).filter((creature) => ( creature.unresolvedComposite || !creature.displayId || !creature.modelPath )); if (unresolved.length) return { status: "blocked", dungeonId: slug, blockers: unresolved.map((creature) => `${creature.name ?? creature.slug} has an unresolved display/composite recipe.`), }; const output = path.join(workRoot, slug, "creature-source"); await runExporter([ "--export-creatures", "--recipe", selected, "--client-root", clientRoot, "--output", output, ]); const blocked = await checkedResult(slug, output); if (blocked) return blocked; return { status: "green", dungeonId: slug, output: path.relative(projectRoot, output).replace(/\\/g, "/") }; }