119 lines · 3.7 KB · TypeScript
RawDownload
import { Log } from "@/util/log"import { Context } from "../util/context"import { Project } from "./project"import { State } from "./state"import { iife } from "@/util/iife"import { GlobalBus } from "@/bus/global"import { Filesystem } from "@/util/filesystem" interface Context {  directory: string  worktree: string  project: Project.Info}const context = Context.create<Context>("instance")const cache = new Map<string, Promise<Context>>() const disposal = {  all: undefined as Promise<void> | undefined,} export const Instance = {  async provide<R>(input: { directory: string; init?: () => Promise<any>; fn: () => R }): Promise<R> {    // Canonicalize so spelling variants of the same folder (trailing slash, `.`,    // symlinks) resolve to one live instance and one project id.    const directory = Project.canonicalize(input.directory)    let existing = cache.get(directory)    if (!existing) {      Log.Default.info("creating instance", { directory })      existing = iife(async () => {        const { project, sandbox } = await Project.fromDirectory(directory)        const ctx = {          directory,          worktree: sandbox,          project,        }        await context.provide(ctx, async () => {          await input.init?.()        })        return ctx      })      cache.set(directory, existing)    }    const ctx = await existing    return context.provide(ctx, async () => {      return input.fn()    })  },  get directory() {    return context.use().directory  },  get worktree() {    return context.use().worktree  },  get project() {    return context.use().project  },  /**   * Check if a path is within the project boundary.   * Returns true if path is inside Instance.directory OR Instance.worktree.   * Paths within the worktree but outside the working directory should not trigger external_directory permission.   */  containsPath(filepath: string) {    if (Filesystem.contains(Instance.directory, filepath)) return true    // Guard against a filesystem-root worktree ("/") matching ANY absolute path,    // which would defeat external_directory permissions. Since folder-scoped    // loading, non-git projects set worktree = the opened directory (not "/"), so    // this only trips for a project literally opened at the fs root.    if (Instance.worktree === "/") return false    return Filesystem.contains(Instance.worktree, filepath)  },  state<S>(init: () => S, dispose?: (state: Awaited<S>) => Promise<void>): () => S {    return State.create(() => Instance.directory, init, dispose)  },  async dispose() {    Log.Default.info("disposing instance", { directory: Instance.directory })    await State.dispose(Instance.directory)    cache.delete(Instance.directory)    GlobalBus.emit("event", {      directory: Instance.directory,      payload: {        type: "server.instance.disposed",        properties: {          directory: Instance.directory,        },      },    })  },  async disposeAll() {    if (disposal.all) return disposal.all     disposal.all = iife(async () => {      Log.Default.info("disposing all instances")      const entries = [...cache.entries()]      for (const [key, value] of entries) {        if (cache.get(key) !== value) continue         const ctx = await value.catch((error) => {          Log.Default.warn("instance dispose failed", { key, error })          return undefined        })         if (!ctx) {          if (cache.get(key) === value) cache.delete(key)          continue        }         if (cache.get(key) !== value) continue         await context.provide(ctx, async () => {          await Instance.dispose()        })      }    }).finally(() => {      disposal.all = undefined    })     return disposal.all  },}