Files
PiAgentUI/extensions/piagentui-extension-manager.test.ts
Evandro b3489e1ede
Build & Push Frontend Docker Image / build-and-push (push) Canceled after 0s
Build Validation / validate (push) Canceled after 6s
Build & Push Backend Docker Image / build-and-push (push) Failing after 10m10s
Build & Push Gateway Docker Image / build-and-push (push) Failing after 10m9s
🎉 Initial commit: PiAgentUI — AI Agent Desktop & Web UI
A professional workspace for AI coding agents with:
- Real-time streaming chat with SSE
- Session management (fork, clone, tree navigation)
- MCP tools browser & metadata panel
- Skills browser (global, project, package sources)
- Voice input & STT provider configuration
- Integrated terminal & file explorer
- Model selector with capability metadata
- Keyboard-first UI with command palette
- 8 languages (en, pt-BR, es, ja, zh-CN, ar, bn, hi)
- Tauri 2 desktop app
- React 19 + TypeScript + Vite 8 + Tailwind CSS 4
- Pi Agent extension runtime

Originally forked from OpenCodeUI, now an independent project.
License: GPL-3.0-only
2026-06-01 00:50:06 -03:00

129 lines
5.4 KiB
TypeScript

import * as fs from 'node:fs'
import * as os from 'node:os'
import * as path from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
buildExtensionCatalog,
executeExtensionInstallCommand,
executeExtensionPackageRemoval,
parsePiInstallCommand,
} from './piagentui-extension-manager.js'
const tempRoots: string[] = []
function makeTempRoot() {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'piagentui-extension-manager-'))
tempRoots.push(root)
return root
}
function writeJson(filePath: string, value: unknown) {
fs.mkdirSync(path.dirname(filePath), { recursive: true })
fs.writeFileSync(filePath, JSON.stringify(value, null, 2), 'utf8')
}
afterEach(() => {
for (const root of tempRoots.splice(0)) {
fs.rmSync(root, { recursive: true, force: true })
}
})
describe('parsePiInstallCommand', () => {
it('extracts source and local scope from a safe Pi install command', () => {
expect(parsePiInstallCommand('pi install npm:pi-hud')).toEqual({ source: 'npm:pi-hud', local: false })
expect(parsePiInstallCommand('pi install npm:@scope/pkg -l')).toEqual({ source: 'npm:@scope/pkg', local: true })
expect(parsePiInstallCommand('pi install -l "D:/Projects/my extension"')).toEqual({
source: 'D:/Projects/my extension',
local: true,
})
})
it('rejects commands that are not a single Pi install operation', () => {
expect(() => parsePiInstallCommand('npm install pi-hud')).toThrow(/pi install/i)
expect(() => parsePiInstallCommand('pi remove npm:pi-hud')).toThrow(/pi install/i)
expect(() => parsePiInstallCommand('pi install npm:pi-hud && rm -rf .')).toThrow(/unsupported/i)
expect(() => parsePiInstallCommand('pi install')).toThrow(/missing/i)
})
})
describe('buildExtensionCatalog', () => {
it('groups configured packages with metadata, resource counts, and active or inactive status', () => {
const homeDir = makeTempRoot()
const cwd = path.join(homeDir, 'project')
const userPackageRoot = path.join(homeDir, '.pi', 'agent', 'npm', 'node_modules', 'pi-demo')
const projectPackageRoot = path.join(cwd, '.pi', 'npm', 'node_modules', 'project-demo')
const missingPackageSource = 'npm:missing-demo'
writeJson(path.join(homeDir, '.pi', 'agent', 'settings.json'), {
packages: ['npm:pi-demo', { source: missingPackageSource, extensions: [] }],
})
writeJson(path.join(cwd, '.pi', 'settings.json'), {
packages: [{ source: 'npm:project-demo', extensions: [] }],
})
writeJson(path.join(userPackageRoot, 'package.json'), {
name: 'pi-demo',
version: '1.2.3',
description: 'Demo package',
pi: { extensions: ['./extensions'], skills: ['./skills'] },
})
fs.mkdirSync(path.join(userPackageRoot, 'extensions'), { recursive: true })
fs.writeFileSync(path.join(userPackageRoot, 'extensions', 'demo.ts'), 'export default {}', 'utf8')
fs.mkdirSync(path.join(userPackageRoot, 'skills', 'demo'), { recursive: true })
fs.writeFileSync(path.join(userPackageRoot, 'skills', 'demo', 'SKILL.md'), '---\nname: demo\n---', 'utf8')
writeJson(path.join(projectPackageRoot, 'package.json'), {
name: 'project-demo',
version: '0.1.0',
description: 'Project package',
pi: { extensions: ['./extensions'] },
})
fs.mkdirSync(path.join(projectPackageRoot, 'extensions'), { recursive: true })
fs.writeFileSync(path.join(projectPackageRoot, 'extensions', 'project.ts'), 'export default {}', 'utf8')
const catalog = buildExtensionCatalog({ cwd, homeDir })
expect(catalog.summary).toEqual({ total: 3, active: 1, inactive: 1, missing: 1, error: 0 })
const userPackage = catalog.packages.find(pkg => pkg.source === 'npm:pi-demo')
expect(userPackage).toMatchObject({
source: 'npm:pi-demo',
scope: 'user',
packageName: 'pi-demo',
version: '1.2.3',
description: 'Demo package',
status: 'active',
installedPath: userPackageRoot,
})
expect(userPackage?.resources.extensions).toEqual([
expect.objectContaining({
name: 'demo.ts',
enabled: true,
path: path.join(userPackageRoot, 'extensions', 'demo.ts'),
}),
])
expect(userPackage?.resources.skills).toEqual([expect.objectContaining({ name: 'demo', enabled: true })])
const projectPackage = catalog.packages.find(pkg => pkg.source === 'npm:project-demo')
expect(projectPackage).toMatchObject({ source: 'npm:project-demo', scope: 'project', status: 'inactive' })
expect(projectPackage?.resources.extensions).toEqual([
expect.objectContaining({ name: 'project.ts', enabled: false }),
])
const missingPackage = catalog.packages.find(pkg => pkg.source === missingPackageSource)
expect(missingPackage).toMatchObject({ source: missingPackageSource, scope: 'user', status: 'missing' })
})
})
describe('extension package operations', () => {
it('runs parsed install and removal through the Pi CLI without executing pasted shell text', async () => {
const runner = vi.fn(async () => ({ stdout: 'ok', stderr: '' }))
await executeExtensionInstallCommand('pi install npm:pi-hud -l', { cwd: '/repo', runner })
await executeExtensionPackageRemoval({ source: 'npm:pi-hud', scope: 'project' }, { cwd: '/repo', runner })
expect(runner).toHaveBeenNthCalledWith(1, ['install', 'npm:pi-hud', '-l'], { cwd: '/repo' })
expect(runner).toHaveBeenNthCalledWith(2, ['remove', 'npm:pi-hud', '-l'], { cwd: '/repo' })
})
})