Files
zmo-cli/packages/opencode/src/app/app.ts
2025-06-03 14:46:28 -04:00

126 lines
2.9 KiB
TypeScript

import { Log } from "../util/log"
import { Context } from "../util/context"
import { Filesystem } from "../util/filesystem"
import { Global } from "../global"
import path from "path"
import { z } from "zod"
export namespace App {
const log = Log.create({ service: "app" })
export const Info = z
.object({
git: z.boolean(),
path: z.object({
data: z.string(),
root: z.string(),
cwd: z.string(),
}),
time: z.object({
initialized: z.number().optional(),
}),
})
.openapi({
ref: "App.Info",
})
export type Info = z.infer<typeof Info>
const ctx = Context.create<Awaited<ReturnType<typeof create>>>("app")
const APP_JSON = "app.json"
async function create(input: { cwd: string; version: string }) {
const git = await Filesystem.findUp(".git", input.cwd).then((x) =>
x ? path.dirname(x) : undefined,
)
const data = path.join(Global.Path.data, git ?? "global")
const stateFile = Bun.file(path.join(data, APP_JSON))
const state = (await stateFile.json().catch(() => ({}))) as {
initialized: number
version: string
}
state.version = input.version
await stateFile.write(JSON.stringify(state))
const services = new Map<
any,
{
state: any
shutdown?: (input: any) => Promise<void>
}
>()
await Log.file(path.join(data, "log"))
const info: Info = {
time: {
initialized: state.initialized,
},
git: git !== undefined,
path: {
data,
root: git ?? input.cwd,
cwd: input.cwd,
},
}
const result = {
version: input.version,
services,
info,
}
return result
}
export function state<State>(
key: any,
init: (app: Info) => State,
shutdown?: (state: Awaited<State>) => Promise<void>,
) {
return () => {
const app = ctx.use()
const services = app.services
if (!services.has(key)) {
log.info("registering service", { name: key })
services.set(key, {
state: init(app.info),
shutdown: shutdown,
})
}
return services.get(key)?.state as State
}
}
export function info() {
return ctx.use().info
}
export async function provide<T extends (app: Info) => any>(
input: { cwd: string; version: string },
cb: T,
) {
const app = await create(input)
return ctx.provide(app, async () => {
const result = await cb(app.info)
for (const [key, entry] of app.services.entries()) {
log.info("shutdown", { name: key })
await entry.shutdown?.(await entry.state)
}
return result
})
}
export async function initialize() {
const { info, version } = ctx.use()
info.time.initialized = Date.now()
await Bun.write(
path.join(info.path.data, APP_JSON),
JSON.stringify({
version,
initialized: Date.now(),
}),
)
}
}