feat: document contribution runtime APIs

This commit is contained in:
mirivlad 2026-06-28 16:41:39 +08:00
parent 8cab72cf29
commit 36846855b6
15 changed files with 319 additions and 19 deletions

View File

@ -10,7 +10,8 @@ for that host-provided object:
- `settings.read/write/writeAll`
- `capabilities.list/get/has`
- `commands.register/execute`
- `commands.register/execute/executeFor`
- `contributions.list`
- `events.publish/subscribe`
- `files.list/metadata/readText/writeText/createFolder/move/trash`
- `workbench.openResource/editResource`
@ -26,6 +27,15 @@ contexts `generic-text`, `generic-markdown`, and `notes-markdown`. Plugins that
request routing declare `workbench.open`; editor/viewer plugins contribute
`contributes.openProviders`. A no-match route returns `status: "no-provider"`.
`contributions.list(point)` returns host-flattened contribution records with
`pluginId`. Files and Notes use this with `commands.executeFor(pluginId,
handler, args)` to run action providers declared by other plugins.
Workspace lifecycle events are `workspace.created`, `workspace.renamed`,
`workspace.trashed`, and `workspace.selected`. Payloads include
`workspaceRootPath` and `workspaceName`; rename/trash events include previous
or trash metadata.
Bundled frontend plugins are trusted/cooperative and run in the desktop JS
context. Current permission checks are contract checks, not a security boundary;
real isolation belongs to a later sidecar/sandbox milestone.

View File

@ -1,4 +1,4 @@
import type { CapabilityEntry, FileEntry, FileMetadata, MovePathOptions, OpenResourceRequest, OpenResourceResult, PluginSettings, TrashResult, WriteTextOptions } from './types';
import type { CapabilityEntry, FileEntry, FileMetadata, MovePathOptions, OpenResourceRequest, OpenResourceResult, PluginSettings, RegisteredContributionPoints, TrashResult, WriteTextOptions } from './types';
export type PluginCommandArgs = Record<string, unknown>;
export type PluginCommandHandler = (args: PluginCommandArgs, declaration: PluginCommandDeclaration) => unknown | Promise<unknown>;
export type Unsubscribe = () => void;
@ -63,6 +63,11 @@ export interface VerstakPluginAPI {
commands: {
register(commandId: string, handler: PluginCommandHandler): Promise<Unsubscribe>;
execute(commandId: string, args?: PluginCommandArgs): Promise<PluginCommandResult>;
executeFor(targetPluginId: string, commandId: string, args?: PluginCommandArgs): Promise<PluginCommandResult>;
};
contributions: {
list(): Promise<RegisteredContributionPoints>;
list<K extends keyof RegisteredContributionPoints>(point: K): Promise<NonNullable<RegisteredContributionPoints[K]>>;
};
events: {
publish(eventName: string, payload?: Record<string, unknown>): Promise<void>;

View File

@ -1 +1 @@
{"version":3,"file":"plugin-api.d.ts","sourceRoot":"","sources":["../src/plugin-api.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"plugin-api.d.ts","sourceRoot":"","sources":["../src/plugin-api.ts"],"names":[],"mappings":"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"}

View File

@ -1 +1 @@
{"version":3,"file":"plugin-api.js","sourceRoot":"","sources":["../src/plugin-api.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,EAAE;AACF,8EAA8E;AAC9E,gFAAgF;AAChF,8EAA8E;AAC9E,gDAAgD;AAkIhD,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC;AACtF,CAAC"}
{"version":3,"file":"plugin-api.js","sourceRoot":"","sources":["../src/plugin-api.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,EAAE;AACF,8EAA8E;AAC9E,gFAAgF;AAChF,8EAA8E;AAC9E,gDAAgD;AA2IhD,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC;AACtF,CAAC"}

View File

@ -1,5 +1,8 @@
import type { PluginManifest, PluginState } from './types';
import type { PluginManifest, PluginState, RegisteredContributionPoints } from './types';
import type { VerstakPluginAPI } from './plugin-api';
export interface MockPluginAPIOptions {
contributions?: RegisteredContributionPoints;
}
/**
* Создать тестовый manifest для unit-тестов.
*/
@ -11,7 +14,7 @@ export declare function createTestPluginState(overrides?: Partial<PluginState>):
/**
* Создать заглушку VerstakPluginAPI для тестов.
*/
export declare function createMockPluginAPI(pluginId?: string): VerstakPluginAPI;
export declare function createMockPluginAPI(pluginId?: string, options?: MockPluginAPIOptions): VerstakPluginAPI;
/**
* Валидатор plugin manifest.
*/

View File

@ -1 +1 @@
{"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAetF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,CASnF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,SAAgB,GAAG,gBAAgB,CAoN9E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAgCxF;AAGD,OAAO,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,EAAE,EAAE,CAAC"}
{"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAC;AACzF,OAAO,KAAK,EAAwB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAQ3E,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,4BAA4B,CAAC;CAC9C;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAetF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,CASnF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,SAAgB,EAAE,OAAO,GAAE,oBAAyB,GAAG,gBAAgB,CA0OlH;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAgCxF;AAGD,OAAO,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,EAAE,EAAE,CAAC"}

33
dist/test-utils.js vendored
View File

@ -1,4 +1,8 @@
// Verstak Plugin SDK — Test Utilities
const mockCommandHandlers = new Map();
function commandKey(pluginId, commandId) {
return `${pluginId}:${commandId}`;
}
/**
* Создать тестовый manifest для unit-тестов.
*/
@ -34,7 +38,7 @@ export function createTestPluginState(overrides) {
/**
* Создать заглушку VerstakPluginAPI для тестов.
*/
export function createMockPluginAPI(pluginId = 'test.plugin') {
export function createMockPluginAPI(pluginId = 'test.plugin', options = {}) {
const settings = {};
const commands = new Map();
const eventHandlers = new Map();
@ -104,14 +108,37 @@ export function createMockPluginAPI(pluginId = 'test.plugin') {
commands: {
register: vi.fn(async (commandId, handler) => {
commands.set(commandId, handler);
return () => { commands.delete(commandId); };
mockCommandHandlers.set(commandKey(pluginId, commandId), handler);
return () => {
commands.delete(commandId);
mockCommandHandlers.delete(commandKey(pluginId, commandId));
};
}),
execute: vi.fn(async (commandId, args = {}) => {
const handler = commands.get(commandId);
if (!handler) {
throw new Error(`declared-but-unhandled: ${commandId}`);
}
return { status: 'handled', pluginId, commandId, result: await handler(args) };
return { status: 'handled', pluginId, commandId, result: await handler(args, { status: 'declared', pluginId, commandId, args }) };
}),
executeFor: vi.fn(async (targetPluginId, commandId, args = {}) => {
const handler = mockCommandHandlers.get(commandKey(targetPluginId, commandId));
if (!handler) {
throw new Error(`declared-but-unhandled: ${targetPluginId}:${commandId}`);
}
return {
status: 'handled',
pluginId: targetPluginId,
commandId,
result: await handler(args, { status: 'declared', pluginId: targetPluginId, commandId, args }),
};
}),
},
contributions: {
list: vi.fn(async (point) => {
if (!point)
return { ...(options.contributions || {}) };
return ([...((options.contributions && options.contributions[point]) || [])]);
}),
},
events: {

File diff suppressed because one or more lines are too long

59
dist/types.d.ts vendored
View File

@ -188,6 +188,23 @@ export interface ContributionWorkspaceItem {
icon?: string;
component: string;
}
export type RegisteredContribution<T> = T & {
pluginId: string;
};
export interface RegisteredContributionPoints {
views?: RegisteredContribution<ContributionView>[];
commands?: RegisteredContribution<ContributionCommand>[];
settingsPanels?: RegisteredContribution<ContributionSettingsPanel>[];
sidebarItems?: RegisteredContribution<ContributionSidebarItem>[];
fileActions?: RegisteredContribution<ContributionAction>[];
noteActions?: RegisteredContribution<ContributionAction>[];
contextMenuEntries?: RegisteredContribution<ContributionContextMenuEntry>[];
searchProviders?: RegisteredContribution<ContributionSearchProvider>[];
activityProviders?: RegisteredContribution<ContributionActivityProvider>[];
statusBarItems?: RegisteredContribution<ContributionStatusBarItem>[];
openProviders?: RegisteredContribution<ContributionOpenProvider>[];
workspaceItems?: RegisteredContribution<ContributionWorkspaceItem>[];
}
export interface OpenResourceContext {
sourcePluginId?: string;
sourceView?: 'files' | 'notes' | string;
@ -290,6 +307,48 @@ export interface NoteSavedEvent extends VerstakEvent {
savedAt: string;
};
}
export interface WorkspaceCreatedEvent extends VerstakEvent {
name: 'workspace.created';
payload: {
operation: 'create';
workspaceRootPath: string;
workspaceName: string;
title?: string;
templateId?: string;
};
}
export interface WorkspaceRenamedEvent extends VerstakEvent {
name: 'workspace.renamed';
payload: {
operation: 'rename';
workspaceRootPath: string;
workspaceName: string;
previousWorkspaceRootPath: string;
previousWorkspaceName: string;
title?: string;
};
}
export interface WorkspaceTrashedEvent extends VerstakEvent {
name: 'workspace.trashed';
payload: {
operation: 'trash';
workspaceRootPath: string;
workspaceName: string;
title?: string;
trashId: string;
trashPath: string;
deletedAt: string;
};
}
export interface WorkspaceSelectedEvent extends VerstakEvent {
name: 'workspace.selected';
payload: {
operation: 'select';
workspaceRootPath: string;
workspaceName: string;
title?: string;
};
}
export interface PluginEnabledEvent extends VerstakEvent {
name: 'plugin.enabled';
payload: {

2
dist/types.d.ts.map vendored

File diff suppressed because one or more lines are too long

View File

@ -71,6 +71,68 @@
"required": ["caseId", "casePath"]
}
},
{
"name": "workspace.created",
"description": "A top-level workspace folder has been created",
"schema": {
"type": "object",
"properties": {
"operation": { "type": "string", "const": "create" },
"workspaceRootPath": { "type": "string", "description": "Vault-relative top-level workspace folder" },
"workspaceName": { "type": "string", "description": "Workspace display/canonical folder name" },
"title": { "type": "string", "description": "Activity title" },
"templateId": { "type": "string", "description": "Template applied during creation, if any" }
},
"required": ["operation", "workspaceRootPath", "workspaceName"]
}
},
{
"name": "workspace.renamed",
"description": "A top-level workspace folder has been renamed",
"schema": {
"type": "object",
"properties": {
"operation": { "type": "string", "const": "rename" },
"workspaceRootPath": { "type": "string", "description": "New workspace root folder" },
"workspaceName": { "type": "string", "description": "New workspace name" },
"previousWorkspaceRootPath": { "type": "string", "description": "Previous workspace root folder" },
"previousWorkspaceName": { "type": "string", "description": "Previous workspace name" },
"title": { "type": "string", "description": "Activity title" }
},
"required": ["operation", "workspaceRootPath", "workspaceName", "previousWorkspaceRootPath", "previousWorkspaceName"]
}
},
{
"name": "workspace.trashed",
"description": "A top-level workspace folder has been moved to internal trash",
"schema": {
"type": "object",
"properties": {
"operation": { "type": "string", "const": "trash" },
"workspaceRootPath": { "type": "string", "description": "Workspace root folder before trash" },
"workspaceName": { "type": "string", "description": "Workspace name before trash" },
"title": { "type": "string", "description": "Activity title" },
"trashId": { "type": "string", "description": "Internal trash item identifier" },
"trashPath": { "type": "string", "description": "Vault-relative trash path" },
"deletedAt": { "type": "string", "description": "ISO 8601 timestamp" }
},
"required": ["operation", "workspaceRootPath", "workspaceName", "trashId", "trashPath", "deletedAt"]
}
},
{
"name": "workspace.selected",
"description": "The current workspace selection has changed",
"schema": {
"type": "object",
"properties": {
"operation": { "type": "string", "const": "select" },
"workspaceRootPath": { "type": "string", "description": "Selected workspace root folder" },
"workspaceName": { "type": "string", "description": "Selected workspace name" },
"title": { "type": "string", "description": "Activity title" }
},
"required": ["operation", "workspaceRootPath", "workspaceName"]
}
},
{
"name": "file.added",
"description": "A file has been added to the vault",

View File

@ -14,6 +14,8 @@ describe('VerstakPluginAPI contract', () => {
expect(typeof api.capabilities.list).toBe('function');
expect(typeof api.commands.register).toBe('function');
expect(typeof api.commands.execute).toBe('function');
expect(typeof api.commands.executeFor).toBe('function');
expect(typeof api.contributions.list).toBe('function');
expect(typeof api.events.publish).toBe('function');
expect(typeof api.events.subscribe).toBe('function');
expect(typeof api.files.list).toBe('function');
@ -188,6 +190,32 @@ describe('VerstakPluginAPI contract', () => {
await expect(api.commands.execute('cmd.plugin.echo', {})).rejects.toThrow('declared-but-unhandled');
});
test('contributions list and provider command execution', async () => {
const api = createMockPluginAPI('consumer.plugin', {
contributions: {
fileActions: [{
pluginId: 'provider.plugin',
id: 'provider.file.action',
label: 'Provider File Action',
handler: 'provider.command',
}],
},
});
const providerApi = createMockPluginAPI('provider.plugin');
await providerApi.commands.register('provider.command', async (args) => args.path);
await expect(api.contributions.list('fileActions')).resolves.toEqual([
expect.objectContaining({ pluginId: 'provider.plugin', id: 'provider.file.action' }),
]);
await expect(api.commands.executeFor('provider.plugin', 'provider.command', { path: 'Docs/readme.md' })).resolves.toEqual({
status: 'handled',
pluginId: 'provider.plugin',
commandId: 'provider.command',
result: 'Docs/readme.md',
});
});
test('events publish to subscribers and unsubscribe cleanly', async () => {
const api = createMockPluginAPI('event.plugin');
const received: unknown[] = [];

View File

@ -13,6 +13,7 @@ import type {
OpenResourceRequest,
OpenResourceResult,
PluginSettings,
RegisteredContributionPoints,
TrashResult,
WriteTextOptions,
} from './types';
@ -88,6 +89,14 @@ export interface VerstakPluginAPI {
commands: {
register(commandId: string, handler: PluginCommandHandler): Promise<Unsubscribe>;
execute(commandId: string, args?: PluginCommandArgs): Promise<PluginCommandResult>;
executeFor(targetPluginId: string, commandId: string, args?: PluginCommandArgs): Promise<PluginCommandResult>;
};
contributions: {
list(): Promise<RegisteredContributionPoints>;
list<K extends keyof RegisteredContributionPoints>(
point: K
): Promise<NonNullable<RegisteredContributionPoints[K]>>;
};
events: {

View File

@ -1,7 +1,17 @@
// Verstak Plugin SDK — Test Utilities
import type { PluginManifest, PluginState } from './types';
import type { VerstakPluginAPI } from './plugin-api';
import type { PluginManifest, PluginState, RegisteredContributionPoints } from './types';
import type { PluginCommandHandler, VerstakPluginAPI } from './plugin-api';
const mockCommandHandlers = new Map<string, PluginCommandHandler>();
function commandKey(pluginId: string, commandId: string): string {
return `${pluginId}:${commandId}`;
}
export interface MockPluginAPIOptions {
contributions?: RegisteredContributionPoints;
}
/**
* Создать тестовый manifest для unit-тестов.
@ -40,9 +50,9 @@ export function createTestPluginState(overrides?: Partial<PluginState>): PluginS
/**
* Создать заглушку VerstakPluginAPI для тестов.
*/
export function createMockPluginAPI(pluginId = 'test.plugin'): VerstakPluginAPI {
export function createMockPluginAPI(pluginId = 'test.plugin', options: MockPluginAPIOptions = {}): VerstakPluginAPI {
const settings: Record<string, unknown> = {};
const commands = new Map<string, (args: Record<string, unknown>) => unknown>();
const commands = new Map<string, PluginCommandHandler>();
const eventHandlers = new Map<string, Array<(event: any) => void>>();
const files = new Map<string, { type: 'file' | 'folder'; content?: string; modifiedAt: string }>();
files.set('', { type: 'folder', modifiedAt: new Date().toISOString() });
@ -107,17 +117,39 @@ export function createMockPluginAPI(pluginId = 'test.plugin'): VerstakPluginAPI
list: vi.fn(async () => []),
},
commands: {
register: vi.fn(async (commandId: string, handler: (args: Record<string, unknown>) => unknown) => {
register: vi.fn(async (commandId: string, handler: PluginCommandHandler) => {
commands.set(commandId, handler);
return () => { commands.delete(commandId); };
mockCommandHandlers.set(commandKey(pluginId, commandId), handler);
return () => {
commands.delete(commandId);
mockCommandHandlers.delete(commandKey(pluginId, commandId));
};
}),
execute: vi.fn(async (commandId: string, args: Record<string, unknown> = {}) => {
const handler = commands.get(commandId);
if (!handler) {
throw new Error(`declared-but-unhandled: ${commandId}`);
}
return { status: 'handled' as const, pluginId, commandId, result: await handler(args) };
return { status: 'handled' as const, pluginId, commandId, result: await handler(args, { status: 'declared', pluginId, commandId, args }) };
}),
executeFor: vi.fn(async (targetPluginId: string, commandId: string, args: Record<string, unknown> = {}) => {
const handler = mockCommandHandlers.get(commandKey(targetPluginId, commandId));
if (!handler) {
throw new Error(`declared-but-unhandled: ${targetPluginId}:${commandId}`);
}
return {
status: 'handled' as const,
pluginId: targetPluginId,
commandId,
result: await handler(args, { status: 'declared', pluginId: targetPluginId, commandId, args }),
};
}),
},
contributions: {
list: vi.fn(async (point?: keyof RegisteredContributionPoints) => {
if (!point) return { ...(options.contributions || {}) };
return ([...((options.contributions && options.contributions[point]) || [])]) as any;
}) as VerstakPluginAPI['contributions']['list'],
},
events: {
publish: vi.fn(async (eventName: string, payload: Record<string, unknown> = {}) => {

View File

@ -251,6 +251,25 @@ export interface ContributionWorkspaceItem {
component: string;
}
export type RegisteredContribution<T> = T & {
pluginId: string;
};
export interface RegisteredContributionPoints {
views?: RegisteredContribution<ContributionView>[];
commands?: RegisteredContribution<ContributionCommand>[];
settingsPanels?: RegisteredContribution<ContributionSettingsPanel>[];
sidebarItems?: RegisteredContribution<ContributionSidebarItem>[];
fileActions?: RegisteredContribution<ContributionAction>[];
noteActions?: RegisteredContribution<ContributionAction>[];
contextMenuEntries?: RegisteredContribution<ContributionContextMenuEntry>[];
searchProviders?: RegisteredContribution<ContributionSearchProvider>[];
activityProviders?: RegisteredContribution<ContributionActivityProvider>[];
statusBarItems?: RegisteredContribution<ContributionStatusBarItem>[];
openProviders?: RegisteredContribution<ContributionOpenProvider>[];
workspaceItems?: RegisteredContribution<ContributionWorkspaceItem>[];
}
export interface OpenResourceContext {
sourcePluginId?: string;
sourceView?: 'files' | 'notes' | string;
@ -380,6 +399,52 @@ export interface NoteSavedEvent extends VerstakEvent {
};
}
export interface WorkspaceCreatedEvent extends VerstakEvent {
name: 'workspace.created';
payload: {
operation: 'create';
workspaceRootPath: string;
workspaceName: string;
title?: string;
templateId?: string;
};
}
export interface WorkspaceRenamedEvent extends VerstakEvent {
name: 'workspace.renamed';
payload: {
operation: 'rename';
workspaceRootPath: string;
workspaceName: string;
previousWorkspaceRootPath: string;
previousWorkspaceName: string;
title?: string;
};
}
export interface WorkspaceTrashedEvent extends VerstakEvent {
name: 'workspace.trashed';
payload: {
operation: 'trash';
workspaceRootPath: string;
workspaceName: string;
title?: string;
trashId: string;
trashPath: string;
deletedAt: string;
};
}
export interface WorkspaceSelectedEvent extends VerstakEvent {
name: 'workspace.selected';
payload: {
operation: 'select';
workspaceRootPath: string;
workspaceName: string;
title?: string;
};
}
// Lifecycle events
export interface PluginEnabledEvent extends VerstakEvent {
name: 'plugin.enabled';