feat(skills): add pluggable no-op runner interface
This commit is contained in:
@@ -19,6 +19,8 @@ import {
|
||||
renderSkillInstallerExecutionStub,
|
||||
toSkillInstallerExecutionStubFromPreflight,
|
||||
evaluateInstallerExecutionPolicy,
|
||||
runInstallerCommandsWithPolicy,
|
||||
noOpSkillInstallerCommandRunner,
|
||||
runSkillInstallAction,
|
||||
} from './skills.js';
|
||||
import type { Skill } from '../skills/index.js';
|
||||
@@ -296,6 +298,36 @@ describe('skills CLI helpers', () => {
|
||||
expect(policy.reason).toBe('execution_disabled');
|
||||
});
|
||||
|
||||
it('does not invoke command runner when policy disables execution', () => {
|
||||
const runner = {
|
||||
run: vi.fn((_commands: string[]) => ['should-not-run']),
|
||||
};
|
||||
|
||||
const executed = runInstallerCommandsWithPolicy(
|
||||
['brew install jq'],
|
||||
{ confirmed: false, execution_enabled: false, reason: 'confirmation_required' },
|
||||
runner,
|
||||
);
|
||||
|
||||
expect(executed).toEqual([]);
|
||||
expect(runner.run).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('supports pluggable command runner when policy enables execution', () => {
|
||||
const runner = {
|
||||
run: vi.fn((commands: string[]) => commands),
|
||||
};
|
||||
|
||||
const executed = runInstallerCommandsWithPolicy(
|
||||
['brew install jq'],
|
||||
{ confirmed: true, execution_enabled: true, reason: 'execution_disabled' },
|
||||
runner,
|
||||
);
|
||||
|
||||
expect(executed).toEqual(['brew install jq']);
|
||||
expect(runner.run).toHaveBeenCalledWith(['brew install jq']);
|
||||
});
|
||||
|
||||
it('summarizes refresh counts across status and tiers', () => {
|
||||
const summary = summarizeSkillsRefresh([
|
||||
buildSkill({ manifest: { name: 'a', description: 'a', version: '1.0.0', tier: 'bundled' } }),
|
||||
@@ -436,6 +468,34 @@ describe('skills CLI helpers', () => {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('uses no-op command runner by default in install flow', () => {
|
||||
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'), '# Install Skill\nInstructions');
|
||||
writeFileSync(
|
||||
join(sourceDir, 'manifest.json'),
|
||||
JSON.stringify({ name: 'install-skill', description: 'Install', version: '1.0.0' }),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
const installer = new SkillInstaller(managedDir);
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
||||
const runnerSpy = vi.spyOn(noOpSkillInstallerCommandRunner, 'run');
|
||||
|
||||
const result = runSkillInstallAction(installer, sourceDir, { mode: 'install', asJson: true, confirmed: true });
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(runnerSpy).not.toHaveBeenCalled();
|
||||
const payload = JSON.parse(String(logSpy.mock.calls[logSpy.mock.calls.length - 1]?.[0]));
|
||||
expect(payload.execution.executed).toEqual([]);
|
||||
|
||||
runnerSpy.mockRestore();
|
||||
logSpy.mockRestore();
|
||||
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'));
|
||||
|
||||
Reference in New Issue
Block a user