Files
msd-core/src/retired-artifact-cleanup.cts
𝚌𝚕𝚎𝚣𝚌𝚘𝚍𝚒𝚗𝚐 88f6d9bd1b fix(#2644): deduplicate Cursor slash menu (#2812)
* fix(#2644): deduplicate Cursor slash menu

* fix: preserve installer executable mode

* chore: add changeset for PR #2812

* test(#2644): acknowledge Cursor emission changes

* test(#2644): drop spent emitted drift acknowledgments

* fix(#2644): remove retired Cursor command converter

---------

Co-authored-by: clezcoding <clezcoding@users.noreply.github.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
2026-08-03 12:05:45 -04:00

103 lines
3.5 KiB
TypeScript

/**
* Conservative cleanup for artifact surfaces retired by a runtime descriptor.
*
* A descriptor may stop materializing an artifact kind while old installs
* still contain files from that surface. Install and profile/surface apply
* both call this helper so either path converges an existing installation.
* Only files proven managed by the previous install manifest are removed;
* modified and unknown files are preserved for the journaled installer
* migration (or the user) to handle without data loss.
*/
import fs from 'node:fs';
import path from 'node:path';
// eslint-disable-next-line @typescript-eslint/no-require-imports
import installerMigrations = require('./installer-migrations.cjs');
import { isPathConfined } from './external-descriptor-trust.cjs';
interface RetiredArtifactDescriptor {
destSubpath?: unknown;
prefix?: unknown;
suffix?: unknown;
}
interface CleanupResult {
removed: string[];
preserved: string[];
}
interface RegistryShape {
runtimes?: Record<string, {
runtime?: {
hostBehaviors?: {
retiredArtifacts?: RetiredArtifactDescriptor[];
};
};
}>;
}
// Generated at build time, so no TypeScript source declaration exists.
// eslint-disable-next-line @typescript-eslint/no-require-imports
const capabilityRegistry = require('./capability-registry.cjs') as unknown as RegistryShape;
function retiredArtifactsFor(runtime: string): RetiredArtifactDescriptor[] {
const configured = capabilityRegistry.runtimes?.[runtime]?.runtime?.hostBehaviors?.retiredArtifacts;
return Array.isArray(configured) ? configured : [];
}
function pruneRetiredRuntimeArtifacts(runtime: string, configDir: string): CleanupResult {
const result: CleanupResult = { removed: [], preserved: [] };
const declarations = retiredArtifactsFor(runtime);
if (declarations.length === 0) return result;
const manifest = installerMigrations.readInstallManifest(configDir);
for (const declaration of declarations) {
const destSubpath = declaration.destSubpath;
const prefix = declaration.prefix;
const suffix = declaration.suffix;
if (
typeof destSubpath !== 'string' || destSubpath.length === 0 ||
typeof prefix !== 'string' || prefix.length === 0 ||
typeof suffix !== 'string' || suffix.length === 0 ||
!isPathConfined(destSubpath, configDir)
) {
continue;
}
const destDir = path.resolve(configDir, destSubpath);
let entries: fs.Dirent[];
try {
if (!fs.existsSync(destDir) || fs.lstatSync(destDir).isSymbolicLink()) continue;
entries = fs.readdirSync(destDir, { withFileTypes: true });
} catch {
continue;
}
for (const entry of entries) {
if (!entry.isFile() || !entry.name.startsWith(prefix) || !entry.name.endsWith(suffix)) continue;
const relPath = path.posix.join(destSubpath.replace(/\\/g, '/'), entry.name);
const artifact = installerMigrations.classifyArtifact(configDir, relPath, manifest);
if (artifact.classification !== 'managed-pristine') {
result.preserved.push(relPath);
continue;
}
try {
fs.unlinkSync(path.join(destDir, entry.name));
result.removed.push(relPath);
} catch {
result.preserved.push(relPath);
}
}
try {
if (fs.readdirSync(destDir).length === 0) fs.rmdirSync(destDir);
} catch {
// Non-empty, unreadable, or concurrently changed: preserve the directory.
}
}
return result;
}
export = { pruneRetiredRuntimeArtifacts, retiredArtifactsFor };