77 lines
2.6 KiB
TypeScript
77 lines
2.6 KiB
TypeScript
////
|
|
// Runs commands and such on the server.
|
|
// This is the "shell" - the "terminal" is the browser UI.
|
|
|
|
import type { CommandResult } from "./shared/types"
|
|
import { sessionStore } from "./session"
|
|
import { commandExists, loadCommandModule } from "./commands"
|
|
import { ALS } from "./session"
|
|
|
|
export async function runCommand(sessionId: string, taskId: string, input: string, ws?: any): Promise<CommandResult> {
|
|
const [cmd = "", ...args] = input.split(" ")
|
|
return runCommandFn({ sessionId, taskId, ws }, async () => exec(cmd, args))
|
|
}
|
|
|
|
export async function runCommandFn(
|
|
{ sessionId, taskId, ws }: { sessionId: string, taskId?: string, ws?: any },
|
|
fn: () => Promise<CommandResult>
|
|
): Promise<CommandResult> {
|
|
try {
|
|
const state = sessionStore(sessionId, taskId, ws)
|
|
return processExecOutput(await ALS.run(state, async () => fn()))
|
|
} catch (err) {
|
|
return { status: "error", output: errorMessage(err) }
|
|
}
|
|
}
|
|
|
|
async function exec(cmd: string, args: string[]): Promise<CommandResult> {
|
|
if (!commandExists(cmd))
|
|
return { status: "error", output: `${cmd} not found` }
|
|
|
|
const module = await loadCommandModule(cmd)
|
|
|
|
if (module?.game)
|
|
return { status: "ok", output: { game: cmd } }
|
|
|
|
if (!module || !module.default)
|
|
return { status: "error", output: `${cmd} has no default export` }
|
|
|
|
return await module.default(...args)
|
|
}
|
|
|
|
export function processExecOutput(output: string | any): CommandResult {
|
|
if (typeof output === "object" && "status" in output && "output" in output)
|
|
return output
|
|
|
|
if (typeof output === "string") {
|
|
return { status: "ok", output }
|
|
} else if (typeof output === "object") {
|
|
if (output.error) {
|
|
return { status: "error", output: output.error }
|
|
} else if (isJSX(output)) {
|
|
return { status: "ok", output: { html: output.toString() } }
|
|
} else if (output.html && isJSX(output.html)) {
|
|
output.html = output.html.toString()
|
|
return { status: "ok", output }
|
|
} else {
|
|
return { status: "ok", output }
|
|
}
|
|
} else if (output === undefined) {
|
|
return { status: "ok", output: "" }
|
|
} else {
|
|
return { status: "ok", output: String(output) }
|
|
}
|
|
}
|
|
|
|
function errorMessage(error: Error | any): string {
|
|
if (!(error instanceof Error))
|
|
return String(error)
|
|
|
|
let msg = `${error.name}: ${error.message}`
|
|
if (error.stack) msg += `\n${error.stack}`
|
|
return msg
|
|
}
|
|
|
|
function isJSX(obj: any): boolean {
|
|
return typeof obj === 'object' && 'tag' in obj && 'props' in obj && 'children' in obj
|
|
} |