feat(skills): guard uninstall with explicit confirmation
This commit is contained in:
+65
-1
@@ -3,7 +3,13 @@ import { mkdtempSync, mkdirSync, writeFileSync, existsSync, rmSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { tmpdir } from 'os';
|
||||
import { SkillInstaller } from '../skills/index.js';
|
||||
import { toSkillListRows, renderSkillsTable, renderSkillInfo, installSkillFromDirectory } from './skills.js';
|
||||
import {
|
||||
toSkillListRows,
|
||||
renderSkillsTable,
|
||||
renderSkillInfo,
|
||||
installSkillFromDirectory,
|
||||
uninstallSkillByName,
|
||||
} from './skills.js';
|
||||
import type { Skill } from '../skills/index.js';
|
||||
|
||||
function buildSkill(overrides: Partial<Skill>): Skill {
|
||||
@@ -129,4 +135,62 @@ describe('skills CLI helpers', () => {
|
||||
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('requires --yes confirmation for uninstall helper', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'flynn-skills-cli-'));
|
||||
const installer = new SkillInstaller(join(root, 'managed'));
|
||||
|
||||
const result = uninstallSkillByName(installer, 'any-skill', { confirm: false });
|
||||
|
||||
expect(result.removed).toBeUndefined();
|
||||
expect(result.error).toContain('--yes');
|
||||
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('uninstalls managed skill when confirmed', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'flynn-skills-cli-'));
|
||||
const sourceDir = join(root, 'source-skill');
|
||||
const managedDir = join(root, 'managed');
|
||||
mkdirSync(sourceDir, { recursive: true });
|
||||
writeFileSync(join(sourceDir, 'SKILL.md'), '# Managed\nInstructions');
|
||||
writeFileSync(
|
||||
join(sourceDir, 'manifest.json'),
|
||||
JSON.stringify({ name: 'managed-skill', description: 'Managed', version: '1.0.0' }),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
const installer = new SkillInstaller(managedDir);
|
||||
installSkillFromDirectory(installer, sourceDir);
|
||||
|
||||
const result = uninstallSkillByName(installer, 'managed-skill', { confirm: true });
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.removed).toBe(true);
|
||||
expect(existsSync(join(managedDir, 'managed-skill', 'SKILL.md'))).toBe(false);
|
||||
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('blocks uninstall of bundled/workspace-only skill', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'flynn-skills-cli-'));
|
||||
const installer = new SkillInstaller(join(root, 'managed'));
|
||||
|
||||
const result = uninstallSkillByName(installer, 'bundled-only', {
|
||||
confirm: true,
|
||||
discoveredSkill: buildSkill({
|
||||
manifest: {
|
||||
name: 'bundled-only',
|
||||
description: 'Bundled',
|
||||
version: '1.0.0',
|
||||
tier: 'bundled',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result.removed).toBeUndefined();
|
||||
expect(result.error).toContain('cannot be uninstalled from managed skills');
|
||||
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user