@sellable/install
Version:
One-command installer for Sellable MCP in Claude Code, Codex, and Hermes
94 lines (85 loc) • 2.38 kB
JavaScript
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,
};
}