diff --git a/examples/data.luau b/examples/data.luau new file mode 100644 index 0000000..4418428 --- /dev/null +++ b/examples/data.luau @@ -0,0 +1,6 @@ +return { + SomeData = { + "This is string data", + 69 + } +} \ No newline at end of file diff --git a/examples/run.ts b/examples/run.ts new file mode 100644 index 0000000..7e955b7 --- /dev/null +++ b/examples/run.ts @@ -0,0 +1,3 @@ +import { SomeData } from "./data.luau"; + +console.log("Received data: ", SomeData) \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..e85c5aa --- /dev/null +++ b/src/index.ts @@ -0,0 +1,19 @@ +import { BunPlugin } from "bun"; + +const plugin: BunPlugin = { + name: "lune", + async setup(build) { + const { importPath } = await import("./loader"); + + build.onLoad({ filter: /\.(luau|lua)$/ }, (args) => { + const mod = importPath(args.path); + + return { + contents: mod, + loader: "json", + }; + }); + }, +}; + +export default plugin; \ No newline at end of file diff --git a/src/loader.luau b/src/loader.luau new file mode 100644 index 0000000..98ed06a --- /dev/null +++ b/src/loader.luau @@ -0,0 +1,41 @@ +local process = require("@lune/process") +local jsonEncode = require("@lune/net").jsonEncode + +local possibleErrors = { + FILE_NOT_FOUND = "No file exist at the path", + NOT_A_MODULE = "Module must return at least one value" +} + +local _, resolvedMod = xpcall(function() + -- [1] - Path to import + -- [2] - Whether module import or not + + local mod = require(process.args[1]) + + if process.args[2] == "MODULE" and mod == nil then + error(possibleErrors.NOT_A_MODULE) + end + + return mod +end, function(err) + -- We only need the msg, not trace + local errMsg = tostring(err):split("stack traceback:")[1] + + for id, msg in possibleErrors do + if errMsg:match(msg) then + print(`[bun-loader-lune::ImportError::{id}] ` .. errMsg) + return + end + end + + print("-- [ImportError] --") + print(tostring(err)) +end) + +if process.args[2] == "MODULE" and typeof(resolvedMod) == "table" then + local modReprJson = jsonEncode(resolvedMod) + + print("--start @generated JS compatible object--") + print(modReprJson) + print("--end @generated JS compatible object--") +end \ No newline at end of file diff --git a/src/loader.ts b/src/loader.ts new file mode 100644 index 0000000..763633b --- /dev/null +++ b/src/loader.ts @@ -0,0 +1,96 @@ +// We need to grab x.luau, then pass the return values of that to another +// function in the luau side. This function parses the table and converts +// it to a JSON object that we can then parse and serve back to the user. + +import { spawnSync, which } from "bun"; +import { exit } from "process"; + +// - Execute lune and run loader script with args for source code +// - return JSON + +export function importPath(path: PathLike): string { + const lunePath = which("lune"); + + if (!lunePath) { + throw new Error( + "[bun-plugin-lune::loader] Cannot find `lune` executable in path", + ); + } + + // Path to luau loader probs shouldnt be hardcoded + const luneChild = spawnSync({ + cmd: [lunePath, "./loader.luau", path.toString(), "MODULE"], + cwd: import.meta.dir, + onExit(_proc, exitCode, _sigCode, err) { + if (exitCode != 0 && exitCode != null) { + console.warn( + `[bun-plugin-lune::loader] \`lune\` process exited with code ${exitCode}`, + ); + + err + ? () => { + console.warn( + "[bun-plugin-lune::loader] Internal error: ", + err.message, + ); + exit(1); + } + : {}; + } + }, + + stderr: "pipe", + stdout: "pipe", + }); + + const luneStderr = luneChild.stderr.toString(); + + if (luneStderr != "") { + const fmtTrace = luneStderr.split("\n") + .map((l) => l = " " + l).join(""); + + console.warn("\nTRACE:"); + console.warn(fmtTrace, "\n"); + + exit(1); + } + + let luneStdout = luneChild.stdout.toString().split("\n"); + let generatedJSONObject: string; + + // luneStdout format: + // ...logs n' stuff + // --start @generated JS compatible object-- + // ...object -- Always is in one line + // --end @generated JS compatible object-- + // Additional newline (\n) + + // Remove the last empty padding newline + luneStdout = luneStdout.filter((v) => v != ""); + luneStdout.every((l, n) => { + // If the stdout line doesn't start with `--`, that means + // it must be a log, and not the generated output + if (l.startsWith("--")) { + if ( + l == "--start @generated JS compatible object--" && + luneStdout[n + 2] == "--end @generated JS compatible object--" + ) { + // The third last index is the end of the generated object + // The next index is the start of the generated object, after the comment + generatedJSONObject = luneStdout.slice(n + 1, n + 2)[0]; + + return false; + } + + console.warn( + "[bun-plugin-lune::loader] invalid JS object returned by internal loader.", + ); + + exit(1); + } else if (process.env.NODE_ENV == "dev") { + console.log(l); + } + }); + + return generatedJSONObject!; +}