UNPKG

anomaly-packer

Version:

Anomaly Packer is a tool that helps in making of an addon for STALKER Anomaly with type-safe code using TypeScript

401 lines (385 loc) 12.5 kB
import { __export } from "./chunk-4VNS5WPM.js"; // src/pack.ts import c from "chalk"; import { existsSync } from "fs"; import fs from "fs/promises"; import iconv from "iconv-lite"; import path2 from "path"; import trash from "trash"; // src/texts/index.ts var texts_exports = {}; __export(texts_exports, { dialog: () => dialog, dltx: () => dltx, include: () => include, infoPortions: () => infoPortions, ltx: () => ltx, specificCharacter: () => specificCharacter, translations: () => translations, ui: () => ui }); // src/texts/character.ts function specificCharacter(character) { return "..."; } // src/texts/dialog.ts import json2xml from "json2xml"; // src/texts/_util.ts function toXmlStructure(tagName, value) { if (value === void 0) return []; return Array.isArray(value) ? value.map((v) => ({ [tagName]: v })) : [ { [tagName]: value } ]; } // src/texts/dialog.ts function dialog(dialog2) { function dialogToJson(dialog3) { return { attrs: { id: dialog3.id }, dialog: [ ...toXmlStructure("precondition", dialog3.precondition), ...toXmlStructure("has_info", dialog3.has_info), ...toXmlStructure("dont_has_info", dialog3.dont_has_info), ...toXmlStructure("give_info", dialog3.give_info), ...toXmlStructure("disable_info", dialog3.disable_info), ...toXmlStructure("init_func", dialog3.init_func), ...toXmlStructure("action", dialog3.action), dialog3.phrases ? { phrase_list: dialog3.phrases.map((phr) => { return { attrs: { id: phr.id }, phrase: [ ...toXmlStructure("precondition", phr.precondition), ...toXmlStructure("has_info", phr.has_info), ...toXmlStructure("dont_has_info", phr.dont_has_info), ...toXmlStructure("text", phr.text), ...toXmlStructure("script_text", phr.script_text), ...toXmlStructure("give_info", phr.give_info), ...toXmlStructure("disable_info", phr.disable_info), ...toXmlStructure("action", phr.action), ...toXmlStructure("is_final", phr.is_final), ...toXmlStructure("next", phr.next) ] }; }) } : {} ] }; } const dialogAsJson = dialogToJson(dialog2); const xml = json2xml(dialogAsJson, { attributes_key: "attrs" }); return xml; } // src/util.ts function objectEntries(obj) { return Object.entries(obj); } // src/texts/dltx.ts function deleteSection({ sectionName }) { let output = ""; output += "!![" + sectionName + "]\n"; return output; } function override(ltx2, sort = true, align = true) { let output = ""; output += "![" + ltx2.sectionName + "]"; if (ltx2._with && ltx2._with.length) { output += ":" + ltx2._with.join(", "); } if (!ltx2.entries) return output; output += "\n"; const entries = objectEntries(ltx2.entries); if (sort) { entries.sort(([k1], [k2]) => k1.localeCompare(k2)); } const [longestKey] = entries.toSorted(([k1], [k2]) => k2.length - k1.length)[0]; for (const [k, v] of entries) { if (k.startsWith("!")) { output += k; } else { output += (align ? k.padEnd(longestKey.length) : k) + " = "; if (Array.isArray(v)) { output += v.join(", "); } else if (v === null) { output += "nil"; } else if (v === void 0) { output += ""; } else { output += v; } } output += "\n"; } return output; } function createOrOverride(ltx2, sort = true, align = true) { let output = ""; output += "@[" + ltx2.sectionName + "]"; if (ltx2._with && ltx2._with.length) { output += ":" + ltx2._with.join(", "); } if (!ltx2.entries) return output; output += "\n"; const entries = objectEntries(ltx2.entries); if (sort) { entries.sort(([k1], [k2]) => k1.localeCompare(k2)); } const [longestKey] = entries.toSorted(([k1], [k2]) => k2.length - k1.length)[0]; for (const [k, v] of entries) { output += (align ? k.padEnd(longestKey.length) : k) + " = "; if (Array.isArray(v)) { output += v.join(", "); } else if (v === null) { output += "nil"; } else { output += v; } output += "\n"; } return output; } var dltx = { createOrOverride, override, deleteSection }; // src/texts/include.ts function include(path3) { return `#include "${path3}"`; } // src/texts/info-portions.ts import json2xml2 from "json2xml"; function infoPortions(portions) { const json = { game_information_portions: objectEntries(portions).map(([id, pe]) => ({ attrs: { id }, info_portion: pe ? [ ...toXmlStructure("action", pe.action), ...toXmlStructure("actor_dialog", pe.actor_dialog), ...toXmlStructure("article", pe.article), ...toXmlStructure("dialog", pe.dialog), ...toXmlStructure("disable", pe.disable) // TODO // location // task ] : [] })) }; const xml = json2xml2(json, { attributes_key: "attrs" }); return xml; } // src/texts/ltx.ts function ltx(ltx2, sort = true, align = true) { let output = ""; output += "[" + ltx2.section + "]"; if (ltx2.with && ltx2.with.length) { output += ":" + ltx2.with.join(", "); } if (!ltx2.entries) return output; output += "\n"; const entries = objectEntries(ltx2.entries); if (sort) { entries.sort(([k1], [k2]) => k1.localeCompare(k2)); } const [longestKey] = entries.toSorted(([k1], [k2]) => k2.length - k1.length)[0]; for (const [k, v] of entries) { output += (align ? k.padEnd(longestKey.length) : k) + " = "; if (Array.isArray(v)) { output += v.join(", "); } else if (v === null) { output += "nil"; } else { output += v; } output += "\n"; } return output; } ltx.f = function(name, ...args) { return name + (args.length ? "(" + args.join(":") + ")" : ""); }; // src/texts/translation.ts import json2xml3 from "json2xml"; function translations(translation, idOverride) { const json = { string_table: objectEntries(translation).map(([id, text]) => { id = idOverride ? idOverride(id) : id; return { string: { text: text ?? "" }, attrs: { id } }; }) }; const xml = json2xml3(json, { attributes_key: "attrs" }); return xml; } // src/texts/ui.ts import json2xml4 from "json2xml"; import { renderToString } from "react-dom/server"; import jsxToJson from "simplified-jsx-to-ast"; function jsxToXml(tree) { const ast = jsxToJson(renderToString(tree)); const fAst = formatAst(ast); const xml = json2xml4(fAst, { attributes_key: "props" }); return xml; } function formatAst(ast) { if (typeof ast === "string") { return ast; } else if (typeof ast === "object") { if (ast.type === "Fragment") { return ast.children; } return { [ast.type]: [...ast.children].map(formatAst), props: ast.props }; } return ast; } var ui = { jsxToXml }; // src/transpilation.ts import path from "path"; import * as tstl from "typescript-to-lua"; async function transpile(scripts) { let transpiledFiles = []; tstl.transpileProject( process.cwd() + "/gamedata/scripts/tsconfig.json", { luaTarget: tstl.LuaTarget.Lua51, luaLibImport: tstl.LuaLibImportKind.Inline, extension: ".script", noHeader: true }, async (buildFileName, text) => { buildFileName = path.basename(buildFileName).replace(".script", ""); const regScript = scripts.find((regScript2) => buildFileName.endsWith(regScript2.sourceFileName)); if (regScript) { buildFileName = regScript.buildFileName ?? regScript.sourceFileName; transpiledFiles.push({ sourceFileName: regScript.sourceFileName, buildFileName, buildFileText: modifyLua(text) }); } } ); return transpiledFiles; } function modifyLua(lua) { lua = globalizeFuncsVars(lua); lua = removeExports(lua); lua = beautifyLibFunctions(lua); return lua; } function beautifyLibFunctions(lua) { return lua.replaceAll(/__TS__(.+?)\(/g, "$1("); } function globalizeFuncsVars(lua) { return lua.replaceAll(/\nlocal\s/g, "\n"); } function removeExports(lua) { return lua.replace(/____exports\.(\w+)\s*=\s*function\s*\(/g, "function $1(").replace(/____exports\./g, "").replace(/local ____exports\s*=\s*{\s*}/, "").replace(/return ____exports/, ""); } // src/pack.ts async function pack(options) { console.log(""); const outDirName = options.build?.outDirName ?? "build"; const cwd = process.cwd(); const buildGamedataPath = path2.join(cwd, outDirName, "gamedata"); if (!existsSync(path2.join(cwd, "gamedata"))) { console.error("gamedata directory must reside in the root of the project, otherwise there is nothing to pack"); return; } else { await fs.rm(buildGamedataPath, { force: true, recursive: true }); await fs.mkdir(buildGamedataPath, { recursive: true }); console.log("Reading " + c.bold.white("gamedata ") + c.reset("directory...")); const scriptsDirPresent = existsSync(path2.join(cwd, "gamedata/scripts")); console.log(c.bold.white("scripts") + c.reset(` directory detected. Transpiling scripts...`)); const transpiled = scriptsDirPresent && options.scripts ? await transpile(options.scripts) : null; await thisRecursiveShit(path2.join(cwd, "gamedata"), buildGamedataPath, transpiled); console.log(c.cyan.bold("Scripts ") + c.cyan("were transpiled")); } if (options.refresh && options.refresh.length) { for (const refresh of options.refresh) { try { await trash(refresh); await fs.cp(buildGamedataPath, refresh, { recursive: true }); } catch (error) { console.error('Refresh path "%s" is incorrect', refresh); console.log(c.gray("Full log: " + error.message)); } } console.log(c.cyan("Build was copied to ") + c.cyan.bold(options.refresh.length + " outer gamedata directories")); for (const refresh of options.refresh) { console.log(" " + c.gray.italic(refresh)); } } console.log(""); } async function thisRecursiveShit(sourcePath, buildPath, allTranspiled) { const dirItems = await fs.readdir(sourcePath); for (const item of dirItems) { const curSourcePath = path2.join(sourcePath, item); const curBuildPath = path2.join(buildPath, item); const itemStat = await fs.stat(curSourcePath); if (itemStat.isDirectory()) { if (curBuildPath.includes(path2.join("gamedata", "scripts")) && !curBuildPath.endsWith(path2.join("gamedata", "scripts"))) { continue; } await fs.mkdir(curBuildPath); await thisRecursiveShit(curSourcePath, curBuildPath, allTranspiled); } else if (itemStat.isFile()) { const ext = path2.extname(item); if (ext === ".ts" || ext === ".tsx") { const fileName = item.substring(0, item.length - ext.length); if (allTranspiled && sourcePath.includes(path2.join("gamedata", "scripts"))) { for (const transpiled of allTranspiled) { if (fileName === transpiled.sourceFileName) { await fs.writeFile( // path2.join(buildPath, transpiled.buildFileName + ".script"), iconv.encode(transpiled.buildFileText, "win1251") ); } } } else { const textScript = await import(curSourcePath); try { const text = await textScript.default(texts_exports); const extension = textScript.extension ?? "xml"; await fs.writeFile(path2.join(buildPath, fileName + `.${extension}`), iconv.encode(text, "win1251")); } catch (e) { console.error("Script at %s does not have a default export or contains an error. This file will not appear in the build", curSourcePath); console.log(c.italic.gray(e.message)); } } } else { if (curBuildPath.includes(path2.join("gamedata", "scripts")) && ext !== ".script") { continue; } const content = await fs.readFile(path2.join(curSourcePath)); await fs.writeFile(path2.join(curBuildPath), iconv.encode(content.toString("utf8"), "win1251")); } } } } export { dialog, dltx, include, infoPortions, ltx, pack, specificCharacter, translations, ui };