100 lines
2.8 KiB
TypeScript
100 lines
2.8 KiB
TypeScript
////
|
|
// Manages the commands on disk, in NOSE_ROOT_BIN and NOSE_BIN
|
|
|
|
import { Glob } from "bun"
|
|
import { watch, readFileSync } from "fs"
|
|
import { join, basename } from "path"
|
|
import { isFile } from "./utils"
|
|
import { sendAll } from "./websocket"
|
|
import { expectDir } from "./utils"
|
|
import type { Command, Commands } from "./shared/types"
|
|
import { projectBin, projectName } from "./project"
|
|
import { DEFAULT_PROJECT, NOSE_DIR, NOSE_ROOT_BIN, NOSE_BIN } from "./config"
|
|
|
|
export function initCommands() {
|
|
startWatchers()
|
|
}
|
|
|
|
export async function commands(project = DEFAULT_PROJECT): Promise<Commands> {
|
|
let binCmds = await findCommands(NOSE_BIN)
|
|
let rootCmds = await findCommands(NOSE_ROOT_BIN)
|
|
let projCmds = project === DEFAULT_PROJECT ? new Map : await findCommands(projectBin())
|
|
|
|
return Object.fromEntries(
|
|
[
|
|
...Object.entries(binCmds),
|
|
...Object.entries(rootCmds),
|
|
...Object.entries(projCmds)
|
|
]
|
|
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
)
|
|
}
|
|
|
|
export async function findCommands(path: string): Promise<Commands> {
|
|
const glob = new Glob("**/*.{ts,tsx}")
|
|
let obj: Commands = {}
|
|
|
|
for await (const file of glob.scan(path))
|
|
obj[file.replace(/\.tsx?$/, "")] = describeCommand(join(path, file))
|
|
|
|
return obj
|
|
}
|
|
|
|
function describeCommand(path: string): Command {
|
|
const code = readFileSync(path, "utf8")
|
|
let game = /^export const game = true$/mg.test(code)
|
|
let browser = /^\/\/\/ ?<reference lib=['"]dom['"]\s*\/mg>$/.test(code)
|
|
|
|
return {
|
|
name: basename(path).replace(/\.tsx?$/, ""),
|
|
type: game ? "game" : browser ? "browser" : "server"
|
|
}
|
|
}
|
|
|
|
export function commandPath(cmd: string): string | undefined {
|
|
let paths = [
|
|
join(NOSE_BIN, cmd + ".ts"),
|
|
join(NOSE_BIN, cmd + ".tsx"),
|
|
join(NOSE_ROOT_BIN, cmd + ".ts"),
|
|
join(NOSE_ROOT_BIN, cmd + ".tsx"),
|
|
]
|
|
|
|
if (projectName() !== DEFAULT_PROJECT)
|
|
paths = paths.concat(
|
|
join(projectBin(), cmd + ".ts"),
|
|
join(projectBin(), cmd + ".tsx"),
|
|
)
|
|
|
|
return paths.find((path: string) => isFile(path))
|
|
}
|
|
|
|
export function commandExists(cmd: string): boolean {
|
|
return commandPath(cmd) !== undefined
|
|
}
|
|
|
|
export async function commandSource(name: string): Promise<string> {
|
|
const path = commandPath(name)
|
|
if (!path) return ""
|
|
return Bun.file(path).text()
|
|
}
|
|
|
|
export async function loadCommandModule(cmd: string) {
|
|
const path = commandPath(cmd)
|
|
if (!path) return
|
|
return await import(path + "?t+" + Date.now())
|
|
}
|
|
|
|
let noseDirWatcher
|
|
let binCmdWatcher
|
|
function startWatchers() {
|
|
if (!expectDir(NOSE_BIN)) return
|
|
if (!expectDir(NOSE_ROOT_BIN)) return
|
|
|
|
binCmdWatcher = watch(NOSE_BIN, async (event, filename) => {
|
|
sendAll({ type: "commands", data: await commands() })
|
|
})
|
|
|
|
noseDirWatcher = watch(NOSE_DIR, async (event, filename) =>
|
|
sendAll({ type: "commands", data: await commands() })
|
|
)
|
|
} |