LosaDev Tools: Lost Saga modding tools in the browser

This commit is contained in:
LosaDev committed 2026-11-07 23:08:43 +07:00
commit c5e04472fb
105 files changed
+38541

No files matched your search

+74
View File
@@ -0,0 +1,74 @@
// Mechanical CommonJS -> ESM conversion of desktop LS Tools modules (no logic changes).
// usage: node tools/port-module.mjs <reference file> <output file>
// const x = require("y"); -> import x from "y";
// const { a, b: c } = require("y"); -> import { a, b as c } from "y";
// module.exports = { A, B: C }; -> export { A, C as B };
// module.exports = X; -> export default X;
// Relative requires get ".js" added. Anything else that is not this simple is reported, so it
// gets ported by hand instead of guessed.
import fs from "node:fs";
import * as acorn from "acorn";
const [src, dst] = process.argv.slice(2);
let text = fs.readFileSync(src, "utf8");
const problems = [];
const fixSpec = (s) => (s.startsWith(".") && !s.endsWith(".js") && !s.endsWith(".json") ? s + ".js" : s);
text = text.replace(/^const (\w+) = require\("([^"]+)"\);?$/gm, (_, name, spec) => `import ${name} from "${fixSpec(spec)}";`);
text = text.replace(/^const \{([^}]+)\} = require\("([^"]+)"\);?$/gm, (_, names, spec) => {
const list = names.split(",").map((n) => n.trim()).filter(Boolean).map((n) => n.replace(/^(\w+)\s*:\s*(\w+)$/, "$1 as $2"));
return `import { ${list.join(", ")} } from "${fixSpec(spec)}";`;
});
// module.exports = { ... } is parsed (acorn), not split on commas: values can be whole arrow
// functions or methods with commas inside.
{
const ast = acorn.parse(text, { ecmaVersion: "latest", sourceType: "module", allowReturnOutsideFunction: true, allowAwaitOutsideFunction: true });
const st = ast.body.find(
(n) =>
n.type === "ExpressionStatement" &&
n.expression.type === "AssignmentExpression" &&
n.expression.left.type === "MemberExpression" &&
n.expression.left.object.name === "module" &&
n.expression.left.property.name === "exports" &&
n.expression.right.type === "ObjectExpression",
);
if (st) {
const consts = [];
const list = [];
for (const p of st.expression.right.properties) {
if (p.type !== "Property" || p.computed) {
problems.push(`module.exports: unsupported property ${text.slice(p.start, p.end).slice(0, 60)}`);
continue;
}
const key = p.key.type === "Identifier" ? p.key.name : String(p.key.value);
if (p.shorthand) list.push([key, key]);
else if (!p.method && p.value.type === "Identifier") list.push([p.value.name, key]);
else if (p.method || p.kind !== "init") {
// method shorthand / getter: keep the exact source inside a one-property object
consts.push(`const __export_${key} = ({ ${text.slice(p.start, p.end)} }).${key};`);
list.push([`__export_${key}`, key]);
} else {
// key: expression (e.g. texture: assets.texture, save: async (...) => {...})
consts.push(`const __export_${key} = ${text.slice(p.value.start, p.value.end)};`);
list.push([`__export_${key}`, key]);
}
}
const named = list.map(([local, key]) => (local === key ? key : `${local} as ${key}`));
// default export too: other modules do `const x = require("./y")` -> `import x from "./y.js"`
const defaults = list.map(([local, key]) => (local === key ? key : `${key}: ${local}`));
const out = `${consts.join("\n")}${consts.length ? "\n" : ""}export { ${named.join(", ")} };\nexport default { ${defaults.join(", ")} };\n`;
text = text.slice(0, st.start) + out + text.slice(st.end);
}
}
text = text.replace(/^module\.exports = (\w+);?\s*$/m, "export default $1;\n");
for (const [i, line] of text.split("\n").entries()) {
if (/\brequire\(/.test(line)) problems.push(`${i + 1}: require left: ${line.trim()}`);
if (/\bmodule\.exports\b|\bexports\.\w+\s*=/.test(line)) problems.push(`${i + 1}: exports left: ${line.trim()}`);
if (/\b__dirname\b|\b__filename\b|process\.(env|platform|argv)/.test(line)) problems.push(`${i + 1}: node global: ${line.trim()}`);
}
fs.mkdirSync(dst.replace(/[\\/][^\\/]+$/, ""), { recursive: true });
fs.writeFileSync(dst, `// Ported from LS Tools desktop: ${src.replace(/\\/g, "/").split("/src/").pop()} (mechanical CJS -> ESM)\n` + text);
console.log(`${dst}: ${problems.length ? problems.length + " to check" : "ok"}`);
for (const p of problems) console.log(" " + p);