LosaDev Tools: Lost Saga modding tools in the browser
This commit is contained in:
commit
c5e04472fb
105 files changed
+38541
No files matched your search
@@ -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);
|
||||
Reference in new issue
Block a user