diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 99c2a13..05d5d60 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -9,6 +9,7 @@ import workspaceRouter from "./routes/workspace.js"; import dockerRouter from "./routes/docker.js"; import editorRouter from "./routes/editor.js"; import terminalRouter from "./routes/terminal.js"; +import buildRouter from "./routes/build.js"; import { attachWebSocket, createTerminalSession } from "./services/terminal.service.js"; import { attachLspWebSocket } from "./services/lsp.service.js"; @@ -33,6 +34,7 @@ app.use("/api/workspace", workspaceRouter); app.use("/api/docker", dockerRouter); app.use("/api/editor", editorRouter); app.use("/api/terminal", terminalRouter); +app.use("/api/build", buildRouter); const frontendDist = path.resolve(__dirname, "../../frontend/dist"); app.use(express.static(frontendDist)); diff --git a/packages/backend/src/routes/build.ts b/packages/backend/src/routes/build.ts new file mode 100644 index 0000000..7d26b28 --- /dev/null +++ b/packages/backend/src/routes/build.ts @@ -0,0 +1,26 @@ +import { Router } from "express"; +import { buildModule } from "../services/build.service.js"; + +const router = Router(); + +router.post("/module/compile", async (req, res) => { + const target = req.body.target || "bare"; + try { + const result = await buildModule(target, "compile"); + res.json(result); + } catch (err: any) { + res.status(500).json({ error: err.message }); + } +}); + +router.post("/module/pack", async (req, res) => { + const target = req.body.target || "bare"; + try { + const result = await buildModule(target, "pack"); + res.json(result); + } catch (err: any) { + res.status(500).json({ error: err.message }); + } +}); + +export default router; diff --git a/packages/backend/src/services/build.service.ts b/packages/backend/src/services/build.service.ts new file mode 100644 index 0000000..27e442c --- /dev/null +++ b/packages/backend/src/services/build.service.ts @@ -0,0 +1,43 @@ +import { runEphemeralContainer } from "./docker.service.js"; +import { + getWorkspacePath, + getServerPath, +} from "./workspace.service.js"; +import { broadcast } from "./ws.service.js"; + +export async function buildModule( + target: string = "bare", + mode: "compile" | "pack" = "compile", +): Promise<{ success: boolean; output: string }> { + const workspacePath = getWorkspacePath(); + const cmd = + mode === "compile" + ? ["nasher", "compile", target, "--yes"] + : ["nasher", "pack", target, "--yes"]; + + broadcast("build", "start", { type: "module", target, mode }); + + const result = await runEphemeralContainer({ + image: "layonara-builder", + cmd, + binds: [ + `${workspacePath}/repos/nwn-module:/build/nwn-module`, + `${workspacePath}/server/modules:/output/modules`, + ], + workingDir: "/build/nwn-module", + }); + + const success = result.statusCode === 0; + broadcast("build", success ? "complete" : "failed", { + type: "module", + target, + mode, + exitCode: result.statusCode, + }); + + if (success && mode === "pack") { + broadcast("build", "info", { message: "Module packed successfully" }); + } + + return { success, output: result.output }; +}