This commit is contained in:
Dax Raad
2025-08-06 23:15:19 -04:00
parent 15e830410f
commit 390b660c81
14 changed files with 584 additions and 193 deletions
+67
View File
@@ -0,0 +1,67 @@
import z from "zod"
import { lazy } from "../util/lazy"
import { Filesystem } from "../util/filesystem"
import path from "path"
import { $ } from "bun"
import { StorageNext } from "../storage/storage-next"
import { Context } from "../util/context"
export namespace Project {
export const Info = z.object({
id: z.string(),
worktree: z.string(),
vcs: z.literal("git").optional(),
})
export type Info = z.infer<typeof Info>
const context = Context.create<Info>("project")
export const use = context.use
export const provide = context.provide
const init = lazy(async () => {
const cwd = process.cwd()
const matches = Filesystem.up({ targets: [".git"], start: cwd })
const git = await matches.next().then((x) => x.value)
await matches.return()
if (!git) {
await StorageNext.write(["project", "global"], {
id: "global",
worktree: "/",
})
return
}
let worktree = path.dirname(git)
const [id] = await $`git rev-list --max-parents=0 --all`
.quiet()
.nothrow()
.cwd(worktree)
.text()
.then((x) =>
x
.split("\n")
.filter(Boolean)
.map((x) => x.trim())
.toSorted(),
)
worktree = path.dirname(
await $`git rev-parse --path-format=absolute --git-common-dir`
.quiet()
.nothrow()
.cwd(worktree)
.text()
.then((x) => x.trim()),
)
await StorageNext.write<Info>(["project", id], {
id,
worktree,
vcs: "git",
})
})
export async function list() {
await init()
const keys = await StorageNext.list(["project"])
return await Promise.all(keys.map((x) => StorageNext.read<Info>(x)))
}
}
+29
View File
@@ -0,0 +1,29 @@
export namespace State {
interface Entry {
state: any
dispose?: (state: any) => Promise<void>
}
const entries = new Map<string, Entry>()
export function create<S>(root: () => string, init: () => S, dispose?: (state: Awaited<S>) => Promise<void>) {
return () => {
const key = root()
const exists = entries.get(key)
if (exists) return exists.state as S
const state = init()
entries.set(key, {
state,
dispose,
})
return state
}
}
export async function dispose() {
for (const [key, entry] of entries.entries()) {
if (!entry.dispose) continue
await entry.dispose(await entry.state)
}
}
}
@@ -0,0 +1,7 @@
import { Context } from "../util/context"
export namespace Worktree {
const context = Context.create<string>("worktree")
export const use = context.use
export const provide = context.provide
}