feat: plugins v1.5

This commit is contained in:
2026-04-29 01:14:30 +02:00
parent 6920f93b41
commit eabbc08896
59 changed files with 2197 additions and 352 deletions

View File

@@ -1,7 +1,10 @@
import { Injector } from '@angular/core';
import type { TojuPluginManifest } from '../../../../shared-kernel';
import { ElectronBridgeService } from '../../../../core/platform/electron/electron-bridge.service';
import { PluginStoreService } from './plugin-store.service';
import { PluginHostService } from './plugin-host.service';
import { PluginDesktopStateService } from './plugin-desktop-state.service';
import { PluginRequirementService } from './plugin-requirement.service';
import { PluginRegistryService } from './plugin-registry.service';
import type { PluginStoreEntry } from '../../domain/models/plugin-store.models';
@@ -71,6 +74,40 @@ describe('PluginStoreService', () => {
]);
});
it('accepts local source manifest paths and resolves relative file links', async () => {
const localSourceManifest = {
plugins: [
{
description: 'Local plugin source.',
id: 'example.local-plugin',
image: './icon.svg',
install: './toju-plugin.json',
readme: './README.md',
title: 'Local Plugin',
version: '1.0.0'
}
],
title: 'Local Plugins'
};
const readFile = vi.fn(async () => toBase64(JSON.stringify(localSourceManifest)));
const service = createService(registerLocalManifest, unregister, { readFile });
await service.addSourceUrl('/home/ludde/Desktop/TestPlugin/plugin-source.json');
expect(fetchMock).not.toHaveBeenCalled();
expect(readFile).toHaveBeenCalledWith('/home/ludde/Desktop/TestPlugin/plugin-source.json');
expect(service.sourceUrls()).toEqual(['file:///home/ludde/Desktop/TestPlugin/plugin-source.json']);
expect(service.availablePlugins()).toEqual([
expect.objectContaining({
id: 'example.local-plugin',
imageUrl: 'file:///home/ludde/Desktop/TestPlugin/icon.svg',
installUrl: 'file:///home/ludde/Desktop/TestPlugin/toju-plugin.json',
readmeUrl: 'file:///home/ludde/Desktop/TestPlugin/README.md',
sourceTitle: 'Local Plugins'
})
]);
});
it('installs, detects updates, and uninstalls store plugins', async () => {
const manifest = createManifest({ version: '1.0.0' });
const plugin = createStoreEntry({ version: '1.0.0' });
@@ -86,7 +123,7 @@ describe('PluginStoreService', () => {
expect(service.getActionLabel(plugin)).toBe('Uninstall');
expect(service.getActionLabel(createStoreEntry({ version: '1.1.0' }))).toBe('Update');
service.uninstallPlugin(plugin.id);
await service.uninstallPlugin(plugin.id);
expect(unregister).toHaveBeenCalledWith(plugin.id);
expect(service.installedPlugins()).toEqual([]);
@@ -111,18 +148,40 @@ describe('PluginStoreService', () => {
function createService(
registerLocalManifest: ReturnType<typeof vi.fn>,
unregister: ReturnType<typeof vi.fn>
unregister: ReturnType<typeof vi.fn>,
electronApi: { readFile: (filePath: string) => Promise<string> } | null = null
): PluginStoreService {
const injector = Injector.create({
providers: [
PluginStoreService,
{
provide: ElectronBridgeService,
useValue: {
getApi: vi.fn(() => electronApi)
}
},
{
provide: PluginHostService,
useValue: { registerLocalManifest }
useValue: {
activatePersistedPlugins: vi.fn(async () => {}),
deactivatePlugin: vi.fn(async () => {}),
registerLocalManifest
}
},
{
provide: PluginDesktopStateService,
useValue: {
readJson: vi.fn(async (_key: string, fallback: unknown) => fallback),
writeJson: vi.fn(async () => undefined)
}
},
{
provide: PluginRegistryService,
useValue: { unregister }
},
{
provide: PluginRequirementService,
useValue: {}
}
]
});
@@ -130,6 +189,10 @@ function createService(
return injector.get(PluginStoreService);
}
function toBase64(value: string): string {
return Buffer.from(value, 'utf8').toString('base64');
}
function createManifest(overrides: Partial<TojuPluginManifest> = {}): TojuPluginManifest {
return {
apiVersion: '1.0.0',