import { applyAutomaticSootsimCleanup, applySootsimCleanup, formatSootsimBytes, planSootsimCleanup, type SootsimCleanupEntry, type SootsimCleanupKind, } from '../../src/disk-cleanup' const KIND_LABELS: Record = { 'browser-cache': 'browser caches', 'camera-fixture': 'staged camera fixture videos', recording: 'recordings and captured frames', 'runtime-archive': 'downloaded runtime archives', 'runtime-version': 'inactive runtime versions', } function summarize(entries: SootsimCleanupEntry[]): string[] { const byKind = new Map() for (const entry of entries) { const current = byKind.get(entry.kind) ?? { bytes: 0, count: 0 } current.bytes += entry.bytes current.count++ byKind.set(entry.kind, current) } return [...byKind] .sort((left, right) => right[1].bytes - left[1].bytes) .map( ([kind, summary]) => ` ${KIND_LABELS[kind]}: ${formatSootsimBytes(summary.bytes)} (${summary.count} ${ summary.count === 1 ? 'item' : 'items' })`, ) } function printHelp(): void { console.log(` rnx cleanup — inspect and reclaim local rnx disk usage usage: rnx cleanup rnx cleanup --apply rnx cleanup --aggressive rnx cleanup --apply --aggressive The default is a read-only preview. --apply removes downloaded runtime archives, old runtimes outside a two-version rollback window, and disposable browser caches. It never removes the active runtime, a runtime serving a connected simulator, cookies, localStorage, IndexedDB, service workers, or other persistent app storage. --aggressive additionally removes the rollback runtime plus user-created recordings and captured frames. It still preserves the active runtime and app storage. `) } export async function runCleanup(args: string[]): Promise { if (args.length === 1 && args[0] === '--automatic-worker') { try { const result = applyAutomaticSootsimCleanup() if (result.status === 'complete') { process.stderr.write( result.reclaimedBytes > 0 ? ` rnx automatic cleanup reclaimed ${formatSootsimBytes( result.reclaimedBytes, )}; active runtimes and app data were kept.\n` : ' rnx automatic cleanup found no obsolete local data.\n', ) } else if (result.status === 'deferred') { process.stderr.write( ` rnx automatic cleanup reclaimed ${formatSootsimBytes( result.reclaimedBytes, )}; ${formatSootsimBytes( result.deferredInUseBytes, )} is in use and will be retried on a later run.\n`, ) } return 0 } catch (error) { process.stderr.write( ` rnx automatic cleanup will retry on the next run: ${ error instanceof Error ? error.message : String(error) }\n`, ) return 0 } } if (args.includes('--help') || args.includes('-h')) { printHelp() return 0 } const known = new Set(['--aggressive', '--apply']) const unknown = args.find((arg) => !known.has(arg)) if (unknown) { console.error(` cleanup: unknown option ${unknown}`) printHelp() return 1 } const aggressive = args.includes('--aggressive') const apply = args.includes('--apply') const plan = planSootsimCleanup({ aggressive }) const selected = plan.entries.filter((entry) => entry.selected) const safe = selected.filter((entry) => entry.state === 'safe') const aggressiveOnly = plan.entries.filter( (entry) => entry.state === 'aggressive' && entry.selected, ) const inUse = plan.entries.filter((entry) => entry.state === 'in-use') const inUseBrowserCaches = inUse.filter((entry) => entry.kind === 'browser-cache') const inUseRuntimes = inUse.filter((entry) => entry.kind === 'runtime-version') const rollback = plan.entries.find( (entry) => entry.kind === 'runtime-version' && entry.state === 'aggressive', ) console.log(` rnx data home: ${plan.home}`) console.log(` current size: ${formatSootsimBytes(plan.totalBytes)}`) console.log(` active runtime: ${plan.activeRuntime ?? '(none installed)'}`) console.log('') console.log(apply ? ' safe items selected for removal:' : ' safe to reclaim:') for (const line of summarize(safe)) console.log(line) if (safe.length === 0) console.log(' nothing') if (aggressiveOnly.length > 0) { console.log('') console.log(' aggressive additions:') for (const line of summarize(aggressiveOnly)) console.log(line) } console.log('') console.log(' protected:') if (plan.activeRuntime) console.log(` active runtime ${plan.activeRuntime} (in use)`) for (const entry of inUseRuntimes) { console.log(` running daemon runtime ${entry.path}`) } if (rollback && !aggressive) { console.log(` rollback runtime ${rollback.path}`) } console.log(' cookies, localStorage, IndexedDB, service workers, and profile data') if (!aggressive) { console.log(' recordings and captured frames (use --aggressive to remove)') } if (inUseBrowserCaches.length > 0) { console.log( ` ${formatSootsimBytes( inUseBrowserCaches.reduce((total, entry) => total + entry.bytes, 0), )} ` + `of browser cache is open; close rnx and rerun cleanup`, ) } console.log('') console.log(` reclaimable now: ${formatSootsimBytes(plan.reclaimableBytes)}`) if (!apply) { console.log( ` preview only — run \`rnx cleanup --apply${ aggressive ? ' --aggressive' : '' }\` to remove these items`, ) return 0 } const result = applySootsimCleanup({ aggressive }) console.log( ` removed ${result.removedPaths.length} ${ result.removedPaths.length === 1 ? 'item' : 'items' }; reclaimed ${formatSootsimBytes(result.reclaimedBytes)}`, ) console.log(` remaining rnx data: ${formatSootsimBytes(result.remainingBytes)}`) return 0 }