import { existsSync } from "node:fs";
import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import type { MetadataStore, WorktreeMetadata, LockInfo } from "../types.js";
import { getRepoRoot } from "./git.js";
const METADATA_FILENAME = "treehouse-metadata.json";
async function getMetadataPath(): Promise<string> {
const repoRoot = await getRepoRoot();
return join(repoRoot, ".git", METADATA_FILENAME);
}
export async function loadMetadata(): Promise<MetadataStore> {
const metadataPath = await getMetadataPath();
if (!existsSync(metadataPath)) {
return { worktrees: [] };
}
try {
const content = await readFile(metadataPath, "utf-8");
return JSON.parse(content);
} catch {
return { worktrees: [] };
}
}
export async function saveMetadata(metadata: MetadataStore): Promise<void> {
const metadataPath = await getMetadataPath();
await writeFile(metadataPath, JSON.stringify(metadata, null, 2));
}
export async function getWorktreeMetadata(
name: string
): Promise<WorktreeMetadata | undefined> {
const metadata = await loadMetadata();
return metadata.worktrees.find((wt) => wt.name === name);
}
export async function trackWorktree(
name: string,
branch: string,
path: string,
lock?: LockInfo
): Promise<void> {
const metadata = await loadMetadata();
const now = new Date().toISOString();
// Remove existing entry
metadata.worktrees = metadata.worktrees.filter((wt) => wt.name !== name);
// Add new entry
metadata.worktrees.push({
name,
branch,
path,
createdAt: now,
lastAccessed: now,
lock,
});
await saveMetadata(metadata);
}
export async function updateLastAccessed(name: string): Promise<void> {
const metadata = await loadMetadata();
const worktree = metadata.worktrees.find((wt) => wt.name === name);
if (worktree) {
worktree.lastAccessed = new Date().toISOString();
await saveMetadata(metadata);
}
}
export async function removeWorktreeMetadata(name: string): Promise<void> {
const metadata = await loadMetadata();
metadata.worktrees = metadata.worktrees.filter((wt) => wt.name !== name);
await saveMetadata(metadata);
}
export async function setWorktreeLock(
name: string,
lock: LockInfo | undefined
): Promise<void> {
const metadata = await loadMetadata();
const worktree = metadata.worktrees.find((wt) => wt.name === name);
if (worktree) {
worktree.lock = lock;
await saveMetadata(metadata);
}
}
export async function isWorktreeLocked(name: string): Promise<boolean> {
const worktree = await getWorktreeMetadata(name);
if (!worktree?.lock) {
return false;
}
// Check if lock has expired
if (worktree.lock.expiresAt) {
const expiresAt = new Date(worktree.lock.expiresAt);
if (expiresAt < new Date()) {
// Lock expired, remove it
await setWorktreeLock(name, undefined);
return false;
}
}
return true;
}
export async function getLockInfo(name: string): Promise<LockInfo | undefined> {
const worktree = await getWorktreeMetadata(name);
if (!worktree?.lock) {
return undefined;
}
// Check if lock has expired
if (worktree.lock.expiresAt) {
const expiresAt = new Date(worktree.lock.expiresAt);
if (expiresAt < new Date()) {
await setWorktreeLock(name, undefined);
return undefined;
}
}
return worktree.lock;
}
export async function getAllWorktreesMetadata(): Promise<WorktreeMetadata[]> {
const metadata = await loadMetadata();
return metadata.worktrees;
}