@@ -365,6 +365,7 @@ const {
|
||||
} = require(path.join(_gsdLibDir, 'runtime-artifact-layout.cjs'));
|
||||
const {
|
||||
createRuntimeArtifactInstallPlan,
|
||||
createRuntimeArtifactUninstallPlan,
|
||||
} = require(path.join(_gsdLibDir, 'runtime-artifact-install-plan.cjs'));
|
||||
const {
|
||||
planLegacyCleanup,
|
||||
@@ -7189,9 +7190,14 @@ function uninstallRuntimeArtifacts(runtime, configDir, scope) {
|
||||
const savedLegacyArtifacts = _runLegacyUninstallCleanup(runtime, configDir, scope);
|
||||
|
||||
const layout = resolveRuntimeArtifactLayout(runtime, configDir, scope);
|
||||
for (const kind of layout.kinds) {
|
||||
const dest = path.join(layout.configDir, kind.destSubpath);
|
||||
_removeGsdEntries(dest, kind);
|
||||
const plan = createRuntimeArtifactUninstallPlan(layout);
|
||||
const kindsByName = new Map(layout.kinds.map((kind) => [kind.kind, kind]));
|
||||
for (const item of plan.items) {
|
||||
const kind = kindsByName.get(item.kind);
|
||||
if (!kind) {
|
||||
throw new Error(`Runtime artifact uninstall plan referenced unknown kind: ${item.kind}`);
|
||||
}
|
||||
_removeGsdEntries(item.destDir, kind);
|
||||
}
|
||||
|
||||
// Hermes: after removing gsd-* skill dirs from skills/gsd/, also remove
|
||||
|
||||
@@ -66,4 +66,12 @@ function createRuntimeArtifactInstallPlan(args) {
|
||||
}
|
||||
return { ok: true, plan: { items, cleanupDirs } };
|
||||
}
|
||||
module.exports = { createRuntimeArtifactInstallPlan };
|
||||
function createRuntimeArtifactUninstallPlan(layout) {
|
||||
return {
|
||||
items: layout.kinds.map((kind) => ({
|
||||
kind: kind.kind,
|
||||
destDir: path.join(layout.configDir, kind.destSubpath),
|
||||
})),
|
||||
};
|
||||
}
|
||||
module.exports = { createRuntimeArtifactInstallPlan, createRuntimeArtifactUninstallPlan };
|
||||
|
||||
@@ -24,6 +24,7 @@ interface ResolvedProfile {
|
||||
interface ArtifactKind {
|
||||
kind: ArtifactKindName;
|
||||
destSubpath: string;
|
||||
prefix?: string;
|
||||
stage: (resolvedProfile: ResolvedProfile) => string;
|
||||
}
|
||||
|
||||
@@ -64,6 +65,15 @@ interface InstallPlan {
|
||||
cleanupDirs: string[];
|
||||
}
|
||||
|
||||
interface UninstallPlanItem {
|
||||
kind: ArtifactKindName;
|
||||
destDir: string;
|
||||
}
|
||||
|
||||
interface UninstallPlan {
|
||||
items: UninstallPlanItem[];
|
||||
}
|
||||
|
||||
type InstallPlanResult =
|
||||
| { ok: true; plan: InstallPlan }
|
||||
| { ok: false; kind: 'stage_failed' | 'rewrite_failed'; message: string; cleanupDirs: string[]; failedKind?: ArtifactKindName };
|
||||
@@ -143,4 +153,13 @@ function createRuntimeArtifactInstallPlan(args: CreateRuntimeArtifactInstallPlan
|
||||
return { ok: true, plan: { items, cleanupDirs } };
|
||||
}
|
||||
|
||||
export = { createRuntimeArtifactInstallPlan };
|
||||
function createRuntimeArtifactUninstallPlan(layout: Layout): UninstallPlan {
|
||||
return {
|
||||
items: layout.kinds.map((kind) => ({
|
||||
kind: kind.kind,
|
||||
destDir: path.join(layout.configDir, kind.destSubpath),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export = { createRuntimeArtifactInstallPlan, createRuntimeArtifactUninstallPlan };
|
||||
|
||||
@@ -47,18 +47,25 @@ const MANIFEST = loadSkillsManifest(REAL_COMMANDS_DIR);
|
||||
const RESOLVED_CORE = resolveProfile({ modes: ['core'], manifest: MANIFEST });
|
||||
|
||||
function loadFreshInstallerWithInstallPlanStub(stub) {
|
||||
return loadFreshInstallerWithPlanStubs({ installStub: stub });
|
||||
}
|
||||
|
||||
function loadFreshInstallerWithPlanStubs({ installStub, uninstallStub }) {
|
||||
const installPath = require.resolve('../bin/install.js');
|
||||
const planPath = require.resolve('../gsd-core/bin/lib/runtime-artifact-install-plan.cjs');
|
||||
const planModule = require(planPath);
|
||||
const original = planModule.createRuntimeArtifactInstallPlan;
|
||||
planModule.createRuntimeArtifactInstallPlan = stub;
|
||||
const originalInstall = planModule.createRuntimeArtifactInstallPlan;
|
||||
const originalUninstall = planModule.createRuntimeArtifactUninstallPlan;
|
||||
if (installStub) planModule.createRuntimeArtifactInstallPlan = installStub;
|
||||
if (uninstallStub) planModule.createRuntimeArtifactUninstallPlan = uninstallStub;
|
||||
delete require.cache[installPath];
|
||||
const installer = require('../bin/install.js');
|
||||
|
||||
return {
|
||||
installer,
|
||||
restore() {
|
||||
planModule.createRuntimeArtifactInstallPlan = original;
|
||||
planModule.createRuntimeArtifactInstallPlan = originalInstall;
|
||||
planModule.createRuntimeArtifactUninstallPlan = originalUninstall;
|
||||
delete require.cache[installPath];
|
||||
},
|
||||
};
|
||||
@@ -401,6 +408,39 @@ describe('installOpencodeFamilySkills — emits skills/<name>/SKILL.md (#784)',
|
||||
|
||||
// ─── Section 7: uninstallRuntimeArtifacts — all runtimes ─────────────────────
|
||||
|
||||
describe('uninstallRuntimeArtifacts — consumes Runtime Artifact Uninstall Plan Module', () => {
|
||||
test('removes returned plan destinations with layout kind metadata', (t) => {
|
||||
const configDir = createTempDir('gsd-uninstall-plan-adapter-');
|
||||
t.after(() => cleanup(configDir));
|
||||
|
||||
const commandsDir = path.join(configDir, 'custom-commands');
|
||||
fs.mkdirSync(commandsDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(commandsDir, 'gsd-help.md'), '# remove\n');
|
||||
fs.writeFileSync(path.join(commandsDir, 'user-custom.md'), '# keep\n');
|
||||
|
||||
let planLayout;
|
||||
const { installer, restore } = loadFreshInstallerWithPlanStubs({
|
||||
uninstallStub(layout) {
|
||||
planLayout = layout;
|
||||
return {
|
||||
items: [
|
||||
{ kind: 'commands', destDir: commandsDir },
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
t.after(restore);
|
||||
|
||||
installer.uninstallRuntimeArtifacts('gemini', configDir, 'global');
|
||||
|
||||
assert.strictEqual(planLayout.runtime, 'gemini');
|
||||
assert.strictEqual(planLayout.configDir, configDir);
|
||||
assert.strictEqual(planLayout.scope, 'global');
|
||||
assert.ok(!fs.existsSync(path.join(commandsDir, 'gsd-help.md')));
|
||||
assert.ok(fs.existsSync(path.join(commandsDir, 'user-custom.md')));
|
||||
});
|
||||
});
|
||||
|
||||
describe('uninstallRuntimeArtifacts — removes gsd-owned entries, preserves foreign', () => {
|
||||
for (const runtime of ALL_RUNTIMES_LAYOUT) {
|
||||
test(`${runtime}: gsd entries removed, foreign preserved`, (t) => {
|
||||
|
||||
Reference in New Issue
Block a user