projects overhaul
This commit is contained in:
parent
dda1cc1f21
commit
4276363a37
|
|
@ -2,7 +2,7 @@
|
||||||
//
|
//
|
||||||
// Show some debugging information.
|
// Show some debugging information.
|
||||||
|
|
||||||
import { NOSE_STARTED, NOSE_SYS_BIN, NOSE_DIR, GIT_SHA } from "@/config"
|
import { NOSE_STARTED, NOSE_SYS_BIN, NOSE_BIN, NOSE_DATA, NOSE_DIR, GIT_SHA } from "@/config"
|
||||||
import { highlightToHTML } from "../lib/highlight"
|
import { highlightToHTML } from "../lib/highlight"
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
|
|
@ -14,7 +14,9 @@ export default function () {
|
||||||
`USER=${valueOrNone(process.env.USER)}`,
|
`USER=${valueOrNone(process.env.USER)}`,
|
||||||
`PWD=${valueOrNone(process.env.PWD)}`,
|
`PWD=${valueOrNone(process.env.PWD)}`,
|
||||||
`NOSE_STARTED=${NOSE_STARTED}`,
|
`NOSE_STARTED=${NOSE_STARTED}`,
|
||||||
|
`NOSE_BIN="${NOSE_BIN}"`,
|
||||||
`NOSE_SYS_BIN="${NOSE_SYS_BIN}"`,
|
`NOSE_SYS_BIN="${NOSE_SYS_BIN}"`,
|
||||||
|
`NOSE_DATA="${NOSE_DATA}"`,
|
||||||
`NOSE_DIR="${NOSE_DIR}"`,
|
`NOSE_DIR="${NOSE_DIR}"`,
|
||||||
`GIT_SHA="${GIT_SHA}"`,
|
`GIT_SHA="${GIT_SHA}"`,
|
||||||
].join("\n"))
|
].join("\n"))
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
import { readdirSync } from "fs"
|
import { readdirSync } from "fs"
|
||||||
import { join } from "path"
|
import { join } from "path"
|
||||||
import { NOSE_SYS_BIN } from "@/config"
|
import { NOSE_BIN } from "@/config"
|
||||||
|
|
||||||
export default async function () {
|
export default async function () {
|
||||||
let games = await Promise.all(readdirSync(NOSE_SYS_BIN, { withFileTypes: true }).map(async file => {
|
let games = await Promise.all(readdirSync(NOSE_BIN, { withFileTypes: true }).map(async file => {
|
||||||
if (!file.isFile()) return
|
if (!file.isFile()) return
|
||||||
|
|
||||||
const code = await Bun.file(join(NOSE_SYS_BIN, file.name)).text()
|
const code = await Bun.file(join(NOSE_BIN, file.name)).text()
|
||||||
|
|
||||||
if (/^export const game\s*=\s*true\s*;?\s*$/m.test(code))
|
if (/^export const game\s*=\s*true\s*;?\s*$/m.test(code))
|
||||||
return file.name.replace(".tsx", "").replace(".ts", "")
|
return file.name.replace(".tsx", "").replace(".ts", "")
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
// Reboot the whole computer! Careful!
|
||||||
export default async function reboot() {
|
export default async function reboot() {
|
||||||
setTimeout(async () => await Bun.$`reboot`, 1000)
|
setTimeout(async () => await Bun.$`reboot`, 1000)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
// Restart the NOSE server.
|
||||||
export default function restart() {
|
export default function restart() {
|
||||||
setTimeout(() => process.exit(), 1000)
|
setTimeout(() => process.exit(), 1000)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
// The git sha for the running server.
|
||||||
import { GIT_SHA } from "@/config"
|
import { GIT_SHA } from "@/config"
|
||||||
export default function () {
|
export default function () {
|
||||||
return GIT_SHA
|
return GIT_SHA
|
||||||
|
|
|
||||||
|
|
@ -7,34 +7,49 @@ import { join } from "path"
|
||||||
import { isFile } from "./utils"
|
import { isFile } from "./utils"
|
||||||
import { sendAll } from "./websocket"
|
import { sendAll } from "./websocket"
|
||||||
import { expectDir } from "./utils"
|
import { expectDir } from "./utils"
|
||||||
import { NOSE_SYS_BIN, NOSE_BIN } from "./config"
|
import { unique } from "./shared/utils"
|
||||||
|
import { projectBin, projectName } from "./project"
|
||||||
|
import { NOSE_DIR, NOSE_SYS_BIN, NOSE_BIN } from "./config"
|
||||||
|
|
||||||
export function initCommands() {
|
export function initCommands() {
|
||||||
startWatchers()
|
startWatchers()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function commands(): Promise<string[]> {
|
export async function commands(project = "sys"): Promise<string[]> {
|
||||||
return (await findCommands(NOSE_SYS_BIN)).concat(await findCommands(NOSE_BIN))
|
let cmds = (await findCommands(NOSE_BIN))
|
||||||
|
.concat(await findCommands(NOSE_SYS_BIN))
|
||||||
|
|
||||||
|
if (project !== "sys")
|
||||||
|
cmds = cmds.concat(await findCommands(projectBin()))
|
||||||
|
|
||||||
|
return unique(cmds).sort()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function findCommands(path: string): Promise<string[]> {
|
export async function findCommands(path: string): Promise<string[]> {
|
||||||
const glob = new Glob("**/*.{ts,tsx}")
|
const glob = new Glob("**/*.{ts,tsx}")
|
||||||
let list: string[] = []
|
let list: string[] = []
|
||||||
|
|
||||||
for await (const file of glob.scan(path)) {
|
for await (const file of glob.scan(path))
|
||||||
list.push(file.replace(".tsx", "").replace(".ts", ""))
|
list.push(file.replace(".tsx", "").replace(".ts", ""))
|
||||||
}
|
|
||||||
|
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
export function commandPath(cmd: string): string | undefined {
|
export function commandPath(cmd: string): string | undefined {
|
||||||
return [
|
let paths = [
|
||||||
|
join(NOSE_BIN, cmd + ".ts"),
|
||||||
|
join(NOSE_BIN, cmd + ".tsx"),
|
||||||
join(NOSE_SYS_BIN, cmd + ".ts"),
|
join(NOSE_SYS_BIN, cmd + ".ts"),
|
||||||
join(NOSE_SYS_BIN, cmd + ".tsx"),
|
join(NOSE_SYS_BIN, cmd + ".tsx"),
|
||||||
join(NOSE_BIN, cmd + ".ts"),
|
]
|
||||||
join(NOSE_BIN, cmd + ".tsx")
|
|
||||||
].find((path: string) => isFile(path))
|
if (projectName() !== "sys")
|
||||||
|
paths.concat([
|
||||||
|
join(projectBin(), cmd + ".ts"),
|
||||||
|
join(projectBin(), cmd + ".tsx"),
|
||||||
|
])
|
||||||
|
|
||||||
|
return paths.find((path: string) => isFile(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function commandExists(cmd: string): boolean {
|
export function commandExists(cmd: string): boolean {
|
||||||
|
|
@ -53,16 +68,17 @@ export async function loadCommandModule(cmd: string) {
|
||||||
return await import(path + "?t+" + Date.now())
|
return await import(path + "?t+" + Date.now())
|
||||||
}
|
}
|
||||||
|
|
||||||
let sysCmdWatcher
|
let noseDirWatcher
|
||||||
let usrCmdWatcher
|
let binCmdWatcher
|
||||||
function startWatchers() {
|
function startWatchers() {
|
||||||
if (!expectDir(NOSE_BIN)) return
|
if (!expectDir(NOSE_BIN)) return
|
||||||
|
if (!expectDir(NOSE_SYS_BIN)) return
|
||||||
|
|
||||||
sysCmdWatcher = watch(NOSE_SYS_BIN, async (event, filename) =>
|
binCmdWatcher = watch(NOSE_BIN, async (event, filename) => {
|
||||||
sendAll({ type: "commands", data: await commands() })
|
|
||||||
)
|
|
||||||
|
|
||||||
usrCmdWatcher = watch(NOSE_BIN, async (event, filename) => {
|
|
||||||
sendAll({ type: "commands", data: await commands() })
|
sendAll({ type: "commands", data: await commands() })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
noseDirWatcher = watch(NOSE_DIR, async (event, filename) =>
|
||||||
|
sendAll({ type: "commands", data: await commands() })
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -4,13 +4,11 @@ import { untilde } from "./utils"
|
||||||
|
|
||||||
export const NOSE_ICON = ` ͡° ͜ʖ ͡°`
|
export const NOSE_ICON = ` ͡° ͜ʖ ͡°`
|
||||||
|
|
||||||
export const NOSE_SYS_BIN = resolve("./bin")
|
export const NOSE_BIN = resolve("./bin")
|
||||||
|
export const NOSE_DATA = resolve("./data")
|
||||||
|
|
||||||
export const NOSE_DIR = resolve(untilde(process.env.NOSE_DIR || "./nose"))
|
export const NOSE_DIR = resolve(untilde(process.env.NOSE_DIR || "./nose"))
|
||||||
export const NOSE_BIN = join(NOSE_DIR, "bin")
|
export const NOSE_SYS_BIN = join(NOSE_DIR, "sys", "bin")
|
||||||
export const NOSE_WWW = join(NOSE_DIR, "www")
|
|
||||||
|
|
||||||
export const NOSE_DATA = resolve("./data")
|
|
||||||
|
|
||||||
export const NOSE_STARTED = Date.now()
|
export const NOSE_STARTED = Date.now()
|
||||||
export const GIT_SHA = (await $`git rev-parse --short HEAD`.text()).trim()
|
export const GIT_SHA = (await $`git rev-parse --short HEAD`.text()).trim()
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
import { watch } from "fs"
|
import { watch } from "fs"
|
||||||
import { apps } from "./webapp"
|
import { apps } from "./webapp"
|
||||||
import { expectDir } from "./utils"
|
import { expectDir } from "./utils"
|
||||||
import { NOSE_WWW } from "./config"
|
import { NOSE_DIR } from "./config"
|
||||||
import { expectShellCmd } from "./utils"
|
import { expectShellCmd } from "./utils"
|
||||||
|
|
||||||
export const dnsEntries: Record<string, any> = {}
|
export const dnsEntries: Record<string, any> = {}
|
||||||
|
|
@ -47,11 +47,11 @@ export function publishAppDNS(app: string) {
|
||||||
return dnsEntries[app]
|
return dnsEntries[app]
|
||||||
}
|
}
|
||||||
|
|
||||||
let wwwWatcher
|
let dnsWatcher
|
||||||
function startWatcher() {
|
function startWatcher() {
|
||||||
if (!expectDir(NOSE_WWW)) return
|
if (!expectDir(NOSE_DIR)) return
|
||||||
|
|
||||||
wwwWatcher = watch(NOSE_WWW, (event, filename) => {
|
dnsWatcher = watch(NOSE_DIR, (event, filename) => {
|
||||||
const www = apps()
|
const www = apps()
|
||||||
www.forEach(publishAppDNS)
|
www.forEach(publishAppDNS)
|
||||||
for (const name in dnsEntries)
|
for (const name in dnsEntries)
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,14 @@ export function projectName(): string {
|
||||||
const state = sessionGet()
|
const state = sessionGet()
|
||||||
if (!state) throw "no state"
|
if (!state) throw "no state"
|
||||||
|
|
||||||
const project = state.project
|
return state.project || "sys"
|
||||||
if (!project) throw "no project loaded"
|
|
||||||
|
|
||||||
return project
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function projects(): string[] {
|
export function projects(): string[] {
|
||||||
return readdirSync(NOSE_DIR, { withFileTypes: true }).filter(file => file.isDirectory()).map(dir => dir.name)
|
return readdirSync(NOSE_DIR, { withFileTypes: true })
|
||||||
|
.filter(file => file.isDirectory())
|
||||||
|
.map(dir => dir.name)
|
||||||
|
.sort()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function projectDir(name = projectName()): string {
|
export function projectDir(name = projectName()): string {
|
||||||
|
|
@ -28,6 +28,10 @@ export function projectDir(name = projectName()): string {
|
||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function projectBin(name = projectName()): string {
|
||||||
|
return join(projectDir(), "bin")
|
||||||
|
}
|
||||||
|
|
||||||
export function projectFiles(name = projectName()): Dirent[] {
|
export function projectFiles(name = projectName()): Dirent[] {
|
||||||
return readdirSync(projectDir(name), { recursive: true, withFileTypes: true })
|
return readdirSync(projectDir(name), { recursive: true, withFileTypes: true })
|
||||||
}
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ import { prettyJSON } from "hono/pretty-json"
|
||||||
import color from "kleur"
|
import color from "kleur"
|
||||||
|
|
||||||
import type { Message } from "./shared/types"
|
import type { Message } from "./shared/types"
|
||||||
import { NOSE_ICON, NOSE_BIN, NOSE_WWW, NOSE_DATA, NOSE_DIR } from "./config"
|
import { NOSE_ICON, NOSE_BIN, NOSE_DATA, NOSE_DIR, NOSE_SYS_BIN } from "./config"
|
||||||
import { transpile, isFile, tilde, isDir } from "./utils"
|
import { transpile, isFile, tilde, isDir } from "./utils"
|
||||||
import { serveApp } from "./webapp"
|
import { serveApp } from "./webapp"
|
||||||
import { commands, commandPath, loadCommandModule } from "./commands"
|
import { commands, commandPath, loadCommandModule } from "./commands"
|
||||||
|
|
@ -127,8 +127,10 @@ app.get("/", c => c.html(<Layout><Terminal /></Layout>))
|
||||||
// websocket
|
// websocket
|
||||||
//
|
//
|
||||||
|
|
||||||
app.get("/ws", upgradeWebSocket(async c => {
|
app.get("/ws", c => {
|
||||||
return {
|
const _sessionId = c.req.query("session")
|
||||||
|
|
||||||
|
return upgradeWebSocket(c, {
|
||||||
async onOpen(_e, ws) {
|
async onOpen(_e, ws) {
|
||||||
addWebsocket(ws)
|
addWebsocket(ws)
|
||||||
send(ws, { type: "commands", data: await commands() })
|
send(ws, { type: "commands", data: await commands() })
|
||||||
|
|
@ -152,8 +154,8 @@ app.get("/ws", upgradeWebSocket(async c => {
|
||||||
await dispatchMessage(ws, data)
|
await dispatchMessage(ws, data)
|
||||||
},
|
},
|
||||||
onClose: (event, ws) => removeWebsocket(ws)
|
onClose: (event, ws) => removeWebsocket(ws)
|
||||||
}
|
})
|
||||||
}))
|
})
|
||||||
|
|
||||||
//
|
//
|
||||||
// hot reload mode cleanup
|
// hot reload mode cleanup
|
||||||
|
|
@ -191,10 +193,10 @@ if (process.env.NODE_ENV === "production") {
|
||||||
//
|
//
|
||||||
|
|
||||||
console.log(color.cyan(NOSE_ICON))
|
console.log(color.cyan(NOSE_ICON))
|
||||||
console.log(color.blue("NOSE_DATA:"), color.yellow(tilde(NOSE_DATA)))
|
console.log(color.blue(" NOSE_BIN:"), color.yellow(tilde(NOSE_BIN)))
|
||||||
console.log(color.blue("NOSE_DIR:"), color.yellow(tilde(NOSE_DIR)))
|
console.log(color.blue(" NOSE_DATA:"), color.yellow(tilde(NOSE_DATA)))
|
||||||
console.log(color.blue("NOSE_BIN:"), color.yellow(tilde(NOSE_BIN)))
|
console.log(color.blue(" NOSE_DIR:"), color.yellow(tilde(NOSE_DIR)))
|
||||||
console.log(color.blue("NOSE_WWW:"), color.yellow(tilde(NOSE_WWW)))
|
console.log(color.blue("NOSE_SYS_BIN:"), color.yellow(tilde(NOSE_SYS_BIN)))
|
||||||
|
|
||||||
await initNoseDir()
|
await initNoseDir()
|
||||||
initCommands()
|
initCommands()
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,16 @@ export type Session = {
|
||||||
const g = globalThis as typeof globalThis & { __thread?: AsyncLocalStorage<Session> }
|
const g = globalThis as typeof globalThis & { __thread?: AsyncLocalStorage<Session> }
|
||||||
export const ALS = g.__thread ??= new AsyncLocalStorage<Session>()
|
export const ALS = g.__thread ??= new AsyncLocalStorage<Session>()
|
||||||
|
|
||||||
|
const sessions: Map<string, Session> = new Map()
|
||||||
|
|
||||||
|
export async function sessionRun(sessionId: string, fn: () => void | Promise<void>) {
|
||||||
|
const state = sessionStore(sessionId)
|
||||||
|
return await ALS.run(state, async () => fn())
|
||||||
|
}
|
||||||
|
|
||||||
export function sessionGet(key?: keyof Session): Session | any | undefined {
|
export function sessionGet(key?: keyof Session): Session | any | undefined {
|
||||||
const store = ALS.getStore()
|
const store = ALS.getStore()
|
||||||
if (!store) return
|
if (!store) throw "sessionGet() called outside of ALS.run"
|
||||||
|
|
||||||
if (key) return store[key]
|
if (key) return store[key]
|
||||||
|
|
||||||
|
|
@ -26,6 +33,18 @@ export function sessionGet(key?: keyof Session): Session | any | undefined {
|
||||||
|
|
||||||
export function sessionSet(key: keyof Session, value: any) {
|
export function sessionSet(key: keyof Session, value: any) {
|
||||||
const store = ALS.getStore()
|
const store = ALS.getStore()
|
||||||
if (!store) return
|
if (!store) throw "sessionSet() called outside of ALS.run"
|
||||||
store[key] = value
|
store[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sessionStore(sessionId: string, taskId?: string, ws?: any): Session {
|
||||||
|
let state = sessions.get(sessionId)
|
||||||
|
if (!state) {
|
||||||
|
state = { sessionId: sessionId, project: "" }
|
||||||
|
sessions.set(sessionId, state)
|
||||||
|
}
|
||||||
|
if (taskId)
|
||||||
|
state.taskId = taskId
|
||||||
|
if (ws) state.ws = ws
|
||||||
|
return state
|
||||||
}
|
}
|
||||||
28
src/shell.ts
28
src/shell.ts
|
|
@ -2,21 +2,14 @@
|
||||||
// Runs commands and such on the server.
|
// Runs commands and such on the server.
|
||||||
// This is the "shell" - the "terminal" is the browser UI.
|
// This is the "shell" - the "terminal" is the browser UI.
|
||||||
|
|
||||||
import type { CommandResult, CommandOutput } from "./shared/types"
|
import type { CommandResult } from "./shared/types"
|
||||||
import type { Session } from "./session"
|
import { sessionStore } from "./session"
|
||||||
import { commandExists, loadCommandModule } from "./commands"
|
import { commandExists, loadCommandModule } from "./commands"
|
||||||
import { ALS } from "./session"
|
import { ALS } from "./session"
|
||||||
|
|
||||||
const sessions: Map<string, Session> = new Map()
|
|
||||||
|
|
||||||
export async function runCommand(sessionId: string, taskId: string, input: string, ws?: any): Promise<CommandResult> {
|
export async function runCommand(sessionId: string, taskId: string, input: string, ws?: any): Promise<CommandResult> {
|
||||||
const [cmd = "", ...args] = input.split(" ")
|
const [cmd = "", ...args] = input.split(" ")
|
||||||
|
|
||||||
if (!commandExists(cmd))
|
|
||||||
return { status: "error", output: `${cmd} not found` }
|
|
||||||
|
|
||||||
return runCommandFn({ sessionId, taskId, ws }, async () => exec(cmd, args))
|
return runCommandFn({ sessionId, taskId, ws }, async () => exec(cmd, args))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runCommandFn(
|
export async function runCommandFn(
|
||||||
|
|
@ -24,7 +17,7 @@ export async function runCommandFn(
|
||||||
fn: () => Promise<CommandResult>
|
fn: () => Promise<CommandResult>
|
||||||
): Promise<CommandResult> {
|
): Promise<CommandResult> {
|
||||||
try {
|
try {
|
||||||
const state = getState(sessionId, taskId, ws)
|
const state = sessionStore(sessionId, taskId, ws)
|
||||||
return processExecOutput(await ALS.run(state, async () => fn()))
|
return processExecOutput(await ALS.run(state, async () => fn()))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { status: "error", output: errorMessage(err) }
|
return { status: "error", output: errorMessage(err) }
|
||||||
|
|
@ -32,6 +25,9 @@ export async function runCommandFn(
|
||||||
}
|
}
|
||||||
|
|
||||||
async function exec(cmd: string, args: string[]): Promise<CommandResult> {
|
async function exec(cmd: string, args: string[]): Promise<CommandResult> {
|
||||||
|
if (!commandExists(cmd))
|
||||||
|
return { status: "error", output: `${cmd} not found` }
|
||||||
|
|
||||||
const module = await loadCommandModule(cmd)
|
const module = await loadCommandModule(cmd)
|
||||||
|
|
||||||
if (module?.game)
|
if (module?.game)
|
||||||
|
|
@ -67,18 +63,6 @@ export function processExecOutput(output: string | any): CommandResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getState(sessionId: string, taskId?: string, ws?: any): Session {
|
|
||||||
let state = sessions.get(sessionId)
|
|
||||||
if (!state) {
|
|
||||||
state = { sessionId: sessionId, project: "" }
|
|
||||||
sessions.set(sessionId, state)
|
|
||||||
}
|
|
||||||
if (taskId)
|
|
||||||
state.taskId = taskId
|
|
||||||
if (ws) state.ws = ws
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
function errorMessage(error: Error | any): string {
|
function errorMessage(error: Error | any): string {
|
||||||
if (!(error instanceof Error))
|
if (!(error instanceof Error))
|
||||||
return String(error)
|
return String(error)
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@
|
||||||
import type { Child } from "hono/jsx"
|
import type { Child } from "hono/jsx"
|
||||||
import { type Context, Hono } from "hono"
|
import { type Context, Hono } from "hono"
|
||||||
import { renderToString } from "hono/jsx/dom/server"
|
import { renderToString } from "hono/jsx/dom/server"
|
||||||
import { join, dirname } from "path"
|
import { join } from "path"
|
||||||
import { readdirSync } from "fs"
|
import { readdirSync } from "fs"
|
||||||
|
|
||||||
import { NOSE_WWW } from "./config"
|
import { NOSE_DIR } from "./config"
|
||||||
import { isFile, isDir } from "./utils"
|
import { isFile, isDir } from "./utils"
|
||||||
|
|
||||||
export type Handler = (r: Context) => string | Child | Response | Promise<Response>
|
export type Handler = (r: Context) => string | Child | Response | Promise<Response>
|
||||||
|
|
@ -35,39 +35,32 @@ export async function serveApp(c: Context, subdomain: string): Promise<Response>
|
||||||
export function apps(): string[] {
|
export function apps(): string[] {
|
||||||
const apps: string[] = []
|
const apps: string[] = []
|
||||||
|
|
||||||
for (const entry of readdirSync(NOSE_WWW))
|
for (const entry of readdirSync(NOSE_DIR))
|
||||||
apps.push(entry.replace(/\.tsx?/, ""))
|
if (isApp(entry))
|
||||||
|
apps.push(entry)
|
||||||
|
|
||||||
return apps.sort()
|
return apps.sort()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isApp(name: string): boolean {
|
||||||
|
return isFile(join(NOSE_DIR, name, "index.ts"))
|
||||||
|
|| isFile(join(NOSE_DIR, name, "index.tsx"))
|
||||||
|
|| isDir(join(NOSE_DIR, name, "pub"))
|
||||||
|
}
|
||||||
|
|
||||||
export function appDir(name: string): string | undefined {
|
export function appDir(name: string): string | undefined {
|
||||||
const path = [
|
if (isApp(name))
|
||||||
`${name}.ts`,
|
return join(NOSE_DIR, name)
|
||||||
`${name}.tsx`,
|
|
||||||
name
|
|
||||||
]
|
|
||||||
.map(path => join(NOSE_WWW, path))
|
|
||||||
.flat()
|
|
||||||
.filter(path => /\.tsx?$/.test(path) ? isFile(path) : isDir(path))[0]
|
|
||||||
|
|
||||||
if (!path) return
|
|
||||||
|
|
||||||
return /\.tsx?$/.test(path) ? dirname(path) : path
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function findApp(name: string): Promise<App | undefined> {
|
async function findApp(name: string): Promise<App | undefined> {
|
||||||
const paths = [
|
const paths = [
|
||||||
`${name}.ts`,
|
|
||||||
`${name}.tsx`,
|
|
||||||
join(name, "index.ts"),
|
join(name, "index.ts"),
|
||||||
join(name, "index.tsx")
|
join(name, "index.tsx")
|
||||||
]
|
]
|
||||||
|
|
||||||
let app
|
|
||||||
|
|
||||||
for (const path of paths) {
|
for (const path of paths) {
|
||||||
app = await loadApp(join(NOSE_WWW, path))
|
const app = await loadApp(join(NOSE_DIR, path))
|
||||||
if (app) return app
|
if (app) return app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user