| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- import "zod-openapi/extend"
- import { Log } from "../util/log"
- import { Context } from "../util/context"
- import { Filesystem } from "../util/filesystem"
- import { Global } from "../global"
- import path from "path"
- import os from "os"
- import { z } from "zod"
- export namespace App {
- const log = Log.create({ service: "app" })
- export const Info = z
- .object({
- hostname: z.string(),
- git: z.boolean(),
- path: z.object({
- config: z.string(),
- data: z.string(),
- root: z.string(),
- cwd: z.string(),
- state: z.string(),
- }),
- time: z.object({
- initialized: z.number().optional(),
- }),
- })
- .openapi({
- ref: "App",
- })
- export type Info = z.infer<typeof Info>
- const ctx = Context.create<{
- info: Info
- services: Map<any, { state: any; shutdown?: (input: any) => Promise<void> }>
- }>("app")
- export const use = ctx.use
- const APP_JSON = "app.json"
- export type Input = {
- cwd: string
- }
- export const provideExisting = ctx.provide
- export async function provide<T>(input: Input, cb: (app: App.Info) => Promise<T>) {
- log.info("creating", {
- cwd: input.cwd,
- })
- const git = await Filesystem.findUp(".git", input.cwd).then(([x]) => (x ? path.dirname(x) : undefined))
- log.info("git", { git })
- const data = path.join(Global.Path.data, "project", git ? directory(git) : "global")
- const stateFile = Bun.file(path.join(data, APP_JSON))
- const state = (await stateFile.json().catch(() => ({}))) as {
- initialized: number
- }
- await stateFile.write(JSON.stringify(state))
- const services = new Map<
- any,
- {
- state: any
- shutdown?: (input: any) => Promise<void>
- }
- >()
- const root = git ?? input.cwd
- const info: Info = {
- hostname: os.hostname(),
- time: {
- initialized: state.initialized,
- },
- git: git !== undefined,
- path: {
- config: Global.Path.config,
- state: Global.Path.state,
- data,
- root,
- cwd: input.cwd,
- },
- }
- const app = {
- services,
- info,
- }
- return ctx.provide(app, async () => {
- try {
- const result = await cb(app.info)
- return result
- } finally {
- for (const [key, entry] of app.services.entries()) {
- if (!entry.shutdown) continue
- log.info("shutdown", { name: key })
- await entry.shutdown?.(await entry.state)
- }
- }
- })
- }
- 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,
- })
- }
- return services.get(key)?.state as State
- }
- }
- export function info() {
- return ctx.use().info
- }
- export async function initialize() {
- const { info } = ctx.use()
- info.time.initialized = Date.now()
- await Bun.write(
- path.join(info.path.data, APP_JSON),
- JSON.stringify({
- initialized: Date.now(),
- }),
- )
- }
- function directory(input: string): string {
- return input
- .split(path.sep)
- .filter(Boolean)
- .join("-")
- .replace(/[^A-Za-z0-9_]/g, "-")
- }
- }
|