import type { AllowedPath } from "../../src/core/paths"; import { configLoader } from "../../src/shared/config"; import type { WorkspaceRootEntry } from "../../src/shared/config/types"; import type { WorkspaceRoot } from "./roots"; /** * An allowed directory outside cwd that /remove-dir can revoke: a workspace * root (session or persisted) or a persisted path-access directory grant. * Session path-access grants live in the path-access extension's ephemeral * memory scope and expire with the session, so they are not listed here. */ export type RemovableEntry = | { kind: "session-root"; path: string } | { kind: "config-root"; scope: "global" | "local"; path: string } | { kind: "path-grant"; scope: "global" | "local"; path: string }; function scopeLabel(scope: "global" | "local"): string { return scope === "local" ? "project" : "global"; } export function describeEntry(entry: RemovableEntry): string { switch (entry.kind) { case "session-root": return `Workspace root (session): ${entry.path}`; case "config-root": return `Workspace root (${scopeLabel(entry.scope)}): ${entry.path}`; case "path-grant": return `Path grant (${scopeLabel(entry.scope)}): ${entry.path}/`; } } function rawRootEntries(raw: Record): WorkspaceRootEntry[] { const workspaceRoots = (raw.workspaceRoots ?? {}) as Record; if (!Array.isArray(workspaceRoots.roots)) return []; return workspaceRoots.roots.filter( (item): item is WorkspaceRootEntry => typeof item === "object" && item !== null && typeof (item as { path?: unknown }).path === "string", ); } function rawDirectoryGrants(raw: Record): AllowedPath[] { const pathAccess = (raw.pathAccess ?? {}) as Record; if (!Array.isArray(pathAccess.allowedPaths)) return []; return pathAccess.allowedPaths.filter( (item): item is AllowedPath => typeof item === "object" && item !== null && (item as { kind?: unknown }).kind === "directory" && typeof (item as { path?: unknown }).path === "string", ); } export function listRemovableEntries( sessionRoots: readonly WorkspaceRoot[], ): RemovableEntry[] { const entries: RemovableEntry[] = sessionRoots.map((root) => ({ kind: "session-root", path: root.path, })); for (const scope of ["global", "local"] as const) { const raw = (configLoader.getRawConfig(scope) ?? {}) as Record< string, unknown >; for (const root of rawRootEntries(raw)) { entries.push({ kind: "config-root", scope, path: root.path }); } for (const grant of rawDirectoryGrants(raw)) { entries.push({ kind: "path-grant", scope, path: grant.path }); } } return entries; } export async function removeConfigRoot( scope: "global" | "local", path: string, ): Promise { const raw = (configLoader.getRawConfig(scope) ?? {}) as Record< string, unknown >; const workspaceRoots = (raw.workspaceRoots ?? {}) as Record; await configLoader.save(scope, { ...raw, workspaceRoots: { ...workspaceRoots, roots: rawRootEntries(raw).filter((root) => root.path !== path), }, }); } export async function removePathGrant( scope: "global" | "local", path: string, ): Promise { const raw = (configLoader.getRawConfig(scope) ?? {}) as Record< string, unknown >; const pathAccess = (raw.pathAccess ?? {}) as Record; const kept = Array.isArray(pathAccess.allowedPaths) ? pathAccess.allowedPaths.filter( (item) => !( typeof item === "object" && item !== null && (item as { kind?: unknown }).kind === "directory" && (item as { path?: unknown }).path === path ), ) : []; await configLoader.save(scope, { ...raw, pathAccess: { ...pathAccess, allowedPaths: kept, }, }); }