//// // 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 { 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 { 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 = /^\/\/\/ ?$/.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 { 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() }) ) }