UNPKG

@sellable/install

Version:

One-command installer for Sellable MCP in Claude Code, Codex, and Hermes

94 lines (85 loc) • 2.38 kB
import { existsSync, lstatSync, readdirSync, rmSync } from "node:fs"; import { basename, dirname, isAbsolute, join, resolve } from "node:path"; const ABANDONED_BACKUP_PREFIXES = Object.freeze([ ".user-state-", ".legacy-user-state-", ]); function canonicalRoot(value, code) { if ( typeof value !== "string" || !isAbsolute(value) || resolve(value) !== value ) { throw new Error(code); } return value; } function directoryEntries(root, code) { if (!existsSync(root)) return []; const stat = lstatSync(root); if (stat.isSymbolicLink() || !stat.isDirectory()) { throw new Error(code); } return readdirSync(root); } function removeDirectDirectory(root, target, code) { if (dirname(target) !== root || basename(target) === "") { throw new Error(code); } if (!existsSync(target)) return false; const stat = lstatSync(target); if (stat.isSymbolicLink() || !stat.isDirectory()) { throw new Error(code); } rmSync(target, { recursive: true }); return true; } /** * A process restart proves these staging directories have no live owner. * Remove only scratch names created by Sellable's snapshot/profile staging; * completed backup archives and the active profile are never candidates. */ export function pruneAbandonedRuntimeScratch({ hermesRoot, profilesRoot }) { const safeHermesRoot = canonicalRoot( hermesRoot, "runtime_scratch_hermes_root_rejected" ); const safeProfilesRoot = canonicalRoot( profilesRoot, "runtime_scratch_profiles_root_rejected" ); const backupsRoot = join(safeHermesRoot, "backups"); const removed = []; for (const name of directoryEntries( backupsRoot, "runtime_scratch_backups_root_rejected" )) { if (!ABANDONED_BACKUP_PREFIXES.some((prefix) => name.startsWith(prefix))) { continue; } if ( removeDirectDirectory( backupsRoot, join(backupsRoot, name), "runtime_scratch_backup_candidate_rejected" ) ) { removed.push(`backups/${name}`); } } const profileStagingRoot = join(safeProfilesRoot, ".staging"); if ( removeDirectDirectory( safeProfilesRoot, profileStagingRoot, "runtime_scratch_profile_staging_rejected" ) ) { removed.push("profiles/.staging"); } return { ok: true, code: "abandoned_runtime_scratch_pruned", removed, }; }