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
JavaScript
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
};