fix(plugin-sdk): aligne le manifeste hello-plugin sur le schéma backend + fixture de test
Le manifeste hello-plugin était généré par un SDK obsolète/désaligné avec le schéma backend actuel (champ ideaPluginManifestVersion manquant, displayName, trustLevel, shape de contributes), ce qui faisait échouer l'installation avec « invalid input: missing field ideaPluginManifestVersion ». - SDK (manifest.ts/js, index.ts) aligné sur le schéma backend courant. - hello-plugin (exemple + fixture) régénéré avec le manifeste valide. - Base de tests fonctionnels plugins : fixture réelle versionnée (reference-minimal) + test d'installation/chargement bout en bout (plugin_install_load.rs), pour couvrir install/load/catalog depuis un dossier fixture réel. Le sous-repo git imbriqué et vide (sdk/IdeaSDK/.git, 0 commit) a été neutralisé pour que les sources du SDK soient suivies normalement dans ce dépôt plutôt que comme gitlink vide. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1
sdk/IdeaSDK/src/index.js
Normal file
1
sdk/IdeaSDK/src/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { isPluginManifest, assertPluginManifest, validatePluginManifest } from "./manifest.js";
|
||||
23
sdk/IdeaSDK/src/index.ts
Normal file
23
sdk/IdeaSDK/src/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
export type {
|
||||
IdeAPluginCapability,
|
||||
IdeAPluginManifest,
|
||||
IdeAPluginEngineConstraints,
|
||||
IdeAPluginLayoutContribution,
|
||||
IdeAPluginMcpServerContribution,
|
||||
IdeAPluginMenuItemContribution,
|
||||
IdeAPluginTopLevelMenuContribution
|
||||
} from "./manifest.js";
|
||||
export {
|
||||
isPluginManifest,
|
||||
assertPluginManifest,
|
||||
validatePluginManifest
|
||||
} from "./manifest.js";
|
||||
export type {
|
||||
ActivateContext,
|
||||
CommandDisposable,
|
||||
CommandHandler,
|
||||
CommandRegistry,
|
||||
IdeAPluginModule,
|
||||
PluginLogger,
|
||||
PluginStorage
|
||||
} from "./runtime.js";
|
||||
172
sdk/IdeaSDK/src/manifest.js
Normal file
172
sdk/IdeaSDK/src/manifest.js
Normal file
@ -0,0 +1,172 @@
|
||||
const PLUGIN_ID_PATTERN = /^[a-z0-9][a-z0-9.-]*[a-z0-9]$/;
|
||||
const SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
||||
export function validatePluginManifest(input) {
|
||||
const errors = [];
|
||||
if (!isRecord(input)) {
|
||||
return { success: false, errors: ["manifest must be an object"] };
|
||||
}
|
||||
if (input.ideaPluginManifestVersion !== 1) {
|
||||
errors.push("ideaPluginManifestVersion must be 1");
|
||||
}
|
||||
requireString(input, "id", errors);
|
||||
requireString(input, "displayName", errors);
|
||||
requireString(input, "version", errors);
|
||||
requireString(input, "main", errors);
|
||||
if (input.trustLevel !== "full") {
|
||||
errors.push("trustLevel must be full");
|
||||
}
|
||||
optionalString(input, "description", errors);
|
||||
optionalString(input, "publisher", errors);
|
||||
if (typeof input.id === "string" && !PLUGIN_ID_PATTERN.test(input.id)) {
|
||||
errors.push("id must contain lowercase letters, digits, dots or dashes, and start/end with an alphanumeric character");
|
||||
}
|
||||
if (typeof input.version === "string" && !SEMVER_PATTERN.test(input.version)) {
|
||||
errors.push("version must use semver syntax, for example 0.1.0");
|
||||
}
|
||||
validateEngines(input.engines, errors);
|
||||
validateCapabilities(input.capabilities, errors);
|
||||
validateContributes(input.contributes, errors);
|
||||
if (errors.length > 0) {
|
||||
return { success: false, errors };
|
||||
}
|
||||
return { success: true, data: input, errors: [] };
|
||||
}
|
||||
export function isPluginManifest(input) {
|
||||
return validatePluginManifest(input).success;
|
||||
}
|
||||
export function assertPluginManifest(input) {
|
||||
const result = validatePluginManifest(input);
|
||||
if (!result.success) {
|
||||
throw new Error(`Invalid IdeA plugin manifest: ${result.errors.join("; ")}`);
|
||||
}
|
||||
}
|
||||
function validateEngines(value, errors) {
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
if (!isRecord(value)) {
|
||||
errors.push("engines must be an object when provided");
|
||||
return;
|
||||
}
|
||||
optionalString(value, "idea", errors, "engines.idea");
|
||||
}
|
||||
function validateCapabilities(value, errors) {
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(value)) {
|
||||
errors.push("capabilities must be an array when provided");
|
||||
return;
|
||||
}
|
||||
value.forEach((capability, index) => {
|
||||
if (capability !== "ui" && capability !== "mcp") {
|
||||
errors.push(`capabilities[${index}] must be "ui" or "mcp"`);
|
||||
}
|
||||
});
|
||||
}
|
||||
function validateContributes(value, errors) {
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
if (!isRecord(value)) {
|
||||
errors.push("contributes must be an object when provided");
|
||||
return;
|
||||
}
|
||||
validateArray(value, "menus", errors, (menu, index) => {
|
||||
requireString(menu, "id", errors, `contributes.menus[${index}].id`);
|
||||
requireString(menu, "label", errors, `contributes.menus[${index}].label`);
|
||||
if (menu.topLevel !== true) {
|
||||
errors.push(`contributes.menus[${index}].topLevel must be true`);
|
||||
}
|
||||
optionalNumber(menu, "order", errors, `contributes.menus[${index}].order`);
|
||||
optionalString(menu, "icon", errors, `contributes.menus[${index}].icon`);
|
||||
});
|
||||
validateArray(value, "menuItems", errors, (item, index) => {
|
||||
requireString(item, "id", errors, `contributes.menuItems[${index}].id`);
|
||||
requireString(item, "targetMenuId", errors, `contributes.menuItems[${index}].targetMenuId`);
|
||||
requireString(item, "label", errors, `contributes.menuItems[${index}].label`);
|
||||
requireString(item, "command", errors, `contributes.menuItems[${index}].command`);
|
||||
optionalNumber(item, "order", errors, `contributes.menuItems[${index}].order`);
|
||||
optionalString(item, "icon", errors, `contributes.menuItems[${index}].icon`);
|
||||
optionalString(item, "when", errors, `contributes.menuItems[${index}].when`);
|
||||
});
|
||||
validateArray(value, "layouts", errors, (layout, index) => {
|
||||
requireString(layout, "type", errors, `contributes.layouts[${index}].type`);
|
||||
requireString(layout, "label", errors, `contributes.layouts[${index}].label`);
|
||||
requireString(layout, "component", errors, `contributes.layouts[${index}].component`);
|
||||
optionalNumber(layout, "order", errors, `contributes.layouts[${index}].order`);
|
||||
optionalString(layout, "icon", errors, `contributes.layouts[${index}].icon`);
|
||||
optionalString(layout, "when", errors, `contributes.layouts[${index}].when`);
|
||||
});
|
||||
validateArray(value, "mcpServers", errors, (server, index) => {
|
||||
requireString(server, "id", errors, `contributes.mcpServers[${index}].id`);
|
||||
requireString(server, "displayName", errors, `contributes.mcpServers[${index}].displayName`);
|
||||
requireString(server, "command", errors, `contributes.mcpServers[${index}].command`);
|
||||
if (server.transport !== "stdio") {
|
||||
errors.push(`contributes.mcpServers[${index}].transport must be "stdio"`);
|
||||
}
|
||||
optionalStringArray(server, "args", errors, `contributes.mcpServers[${index}].args`);
|
||||
optionalStringRecord(server, "env", errors, `contributes.mcpServers[${index}].env`);
|
||||
optionalString(server, "cwd", errors, `contributes.mcpServers[${index}].cwd`);
|
||||
optionalBoolean(server, "autoStart", errors, `contributes.mcpServers[${index}].autoStart`);
|
||||
optionalBoolean(server, "allowAbsoluteCommand", errors, `contributes.mcpServers[${index}].allowAbsoluteCommand`);
|
||||
});
|
||||
}
|
||||
function validateArray(record, key, errors, validateItem) {
|
||||
const value = record[key];
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(value)) {
|
||||
errors.push(`contributes.${key} must be an array when provided`);
|
||||
return;
|
||||
}
|
||||
value.forEach((item, index) => {
|
||||
if (!isRecord(item)) {
|
||||
errors.push(`contributes.${key}[${index}] must be an object`);
|
||||
return;
|
||||
}
|
||||
validateItem(item, index);
|
||||
});
|
||||
}
|
||||
function requireString(record, key, errors, label = key) {
|
||||
if (typeof record[key] !== "string" || record[key].trim().length === 0) {
|
||||
errors.push(`${label} must be a non-empty string`);
|
||||
}
|
||||
}
|
||||
function optionalString(record, key, errors, label = key) {
|
||||
if (record[key] !== undefined && typeof record[key] !== "string") {
|
||||
errors.push(`${label} must be a string when provided`);
|
||||
}
|
||||
}
|
||||
function optionalNumber(record, key, errors, label = key) {
|
||||
if (record[key] !== undefined && typeof record[key] !== "number") {
|
||||
errors.push(`${label} must be a number when provided`);
|
||||
}
|
||||
}
|
||||
function optionalBoolean(record, key, errors, label = key) {
|
||||
if (record[key] !== undefined && typeof record[key] !== "boolean") {
|
||||
errors.push(`${label} must be a boolean when provided`);
|
||||
}
|
||||
}
|
||||
function optionalStringArray(record, key, errors, label = key) {
|
||||
const value = record[key];
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
||||
errors.push(`${label} must be an array of strings when provided`);
|
||||
}
|
||||
}
|
||||
function optionalStringRecord(record, key, errors, label = key) {
|
||||
const value = record[key];
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
if (!isRecord(value) || Object.values(value).some((item) => typeof item !== "string")) {
|
||||
errors.push(`${label} must be an object of strings when provided`);
|
||||
}
|
||||
}
|
||||
function isRecord(value) {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
309
sdk/IdeaSDK/src/manifest.ts
Normal file
309
sdk/IdeaSDK/src/manifest.ts
Normal file
@ -0,0 +1,309 @@
|
||||
export interface IdeAPluginManifest {
|
||||
ideaPluginManifestVersion: 1;
|
||||
id: string;
|
||||
displayName: string;
|
||||
version: string;
|
||||
main: string;
|
||||
trustLevel: "full";
|
||||
description?: string;
|
||||
publisher?: string;
|
||||
engines?: IdeAPluginEngineConstraints;
|
||||
capabilities?: IdeAPluginCapability[];
|
||||
contributes?: {
|
||||
menus?: IdeAPluginTopLevelMenuContribution[];
|
||||
menuItems?: IdeAPluginMenuItemContribution[];
|
||||
layouts?: IdeAPluginLayoutContribution[];
|
||||
mcpServers?: IdeAPluginMcpServerContribution[];
|
||||
};
|
||||
}
|
||||
|
||||
export type IdeAPluginCapability = "ui" | "mcp";
|
||||
|
||||
export interface IdeAPluginEngineConstraints {
|
||||
idea?: string;
|
||||
}
|
||||
|
||||
export interface IdeAPluginTopLevelMenuContribution {
|
||||
id: string;
|
||||
label: string;
|
||||
topLevel: true;
|
||||
order?: number;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export interface IdeAPluginMenuItemContribution {
|
||||
id: string;
|
||||
targetMenuId: string;
|
||||
label: string;
|
||||
command: string;
|
||||
order?: number;
|
||||
icon?: string;
|
||||
when?: string;
|
||||
}
|
||||
|
||||
export interface IdeAPluginLayoutContribution {
|
||||
type: string;
|
||||
label: string;
|
||||
component: string;
|
||||
order?: number;
|
||||
icon?: string;
|
||||
when?: string;
|
||||
}
|
||||
|
||||
export interface IdeAPluginMcpServerContribution {
|
||||
id: string;
|
||||
displayName: string;
|
||||
command: string;
|
||||
args?: string[];
|
||||
env?: Record<string, string>;
|
||||
cwd?: string;
|
||||
transport: "stdio";
|
||||
autoStart?: boolean;
|
||||
allowAbsoluteCommand?: boolean;
|
||||
}
|
||||
|
||||
export type PluginManifestValidationResult =
|
||||
| { success: true; data: IdeAPluginManifest; errors: [] }
|
||||
| { success: false; data?: undefined; errors: string[] };
|
||||
|
||||
const PLUGIN_ID_PATTERN = /^[a-z0-9][a-z0-9.-]*[a-z0-9]$/;
|
||||
const SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
||||
|
||||
export function validatePluginManifest(input: unknown): PluginManifestValidationResult {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!isRecord(input)) {
|
||||
return { success: false, errors: ["manifest must be an object"] };
|
||||
}
|
||||
|
||||
if (input.ideaPluginManifestVersion !== 1) {
|
||||
errors.push("ideaPluginManifestVersion must be 1");
|
||||
}
|
||||
|
||||
requireString(input, "id", errors);
|
||||
requireString(input, "displayName", errors);
|
||||
requireString(input, "version", errors);
|
||||
requireString(input, "main", errors);
|
||||
if (input.trustLevel !== "full") {
|
||||
errors.push("trustLevel must be full");
|
||||
}
|
||||
optionalString(input, "description", errors);
|
||||
optionalString(input, "publisher", errors);
|
||||
|
||||
if (typeof input.id === "string" && !PLUGIN_ID_PATTERN.test(input.id)) {
|
||||
errors.push("id must contain lowercase letters, digits, dots or dashes, and start/end with an alphanumeric character");
|
||||
}
|
||||
|
||||
if (typeof input.version === "string" && !SEMVER_PATTERN.test(input.version)) {
|
||||
errors.push("version must use semver syntax, for example 0.1.0");
|
||||
}
|
||||
|
||||
validateEngines(input.engines, errors);
|
||||
validateCapabilities(input.capabilities, errors);
|
||||
validateContributes(input.contributes, errors);
|
||||
|
||||
if (errors.length > 0) {
|
||||
return { success: false, errors };
|
||||
}
|
||||
|
||||
return { success: true, data: input as unknown as IdeAPluginManifest, errors: [] };
|
||||
}
|
||||
|
||||
export function isPluginManifest(input: unknown): input is IdeAPluginManifest {
|
||||
return validatePluginManifest(input).success;
|
||||
}
|
||||
|
||||
export function assertPluginManifest(input: unknown): asserts input is IdeAPluginManifest {
|
||||
const result = validatePluginManifest(input);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(`Invalid IdeA plugin manifest: ${result.errors.join("; ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
function validateEngines(value: unknown, errors: string[]): void {
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isRecord(value)) {
|
||||
errors.push("engines must be an object when provided");
|
||||
return;
|
||||
}
|
||||
|
||||
optionalString(value, "idea", errors, "engines.idea");
|
||||
}
|
||||
|
||||
function validateCapabilities(value: unknown, errors: string[]): void {
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(value)) {
|
||||
errors.push("capabilities must be an array when provided");
|
||||
return;
|
||||
}
|
||||
|
||||
value.forEach((capability, index) => {
|
||||
if (capability !== "ui" && capability !== "mcp") {
|
||||
errors.push(`capabilities[${index}] must be "ui" or "mcp"`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validateContributes(value: unknown, errors: string[]): void {
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isRecord(value)) {
|
||||
errors.push("contributes must be an object when provided");
|
||||
return;
|
||||
}
|
||||
|
||||
validateArray(value, "menus", errors, (menu, index) => {
|
||||
requireString(menu, "id", errors, `contributes.menus[${index}].id`);
|
||||
requireString(menu, "label", errors, `contributes.menus[${index}].label`);
|
||||
if (menu.topLevel !== true) {
|
||||
errors.push(`contributes.menus[${index}].topLevel must be true`);
|
||||
}
|
||||
optionalNumber(menu, "order", errors, `contributes.menus[${index}].order`);
|
||||
optionalString(menu, "icon", errors, `contributes.menus[${index}].icon`);
|
||||
});
|
||||
validateArray(value, "menuItems", errors, (item, index) => {
|
||||
requireString(item, "id", errors, `contributes.menuItems[${index}].id`);
|
||||
requireString(item, "targetMenuId", errors, `contributes.menuItems[${index}].targetMenuId`);
|
||||
requireString(item, "label", errors, `contributes.menuItems[${index}].label`);
|
||||
requireString(item, "command", errors, `contributes.menuItems[${index}].command`);
|
||||
optionalNumber(item, "order", errors, `contributes.menuItems[${index}].order`);
|
||||
optionalString(item, "icon", errors, `contributes.menuItems[${index}].icon`);
|
||||
optionalString(item, "when", errors, `contributes.menuItems[${index}].when`);
|
||||
});
|
||||
validateArray(value, "layouts", errors, (layout, index) => {
|
||||
requireString(layout, "type", errors, `contributes.layouts[${index}].type`);
|
||||
requireString(layout, "label", errors, `contributes.layouts[${index}].label`);
|
||||
requireString(layout, "component", errors, `contributes.layouts[${index}].component`);
|
||||
optionalNumber(layout, "order", errors, `contributes.layouts[${index}].order`);
|
||||
optionalString(layout, "icon", errors, `contributes.layouts[${index}].icon`);
|
||||
optionalString(layout, "when", errors, `contributes.layouts[${index}].when`);
|
||||
});
|
||||
validateArray(value, "mcpServers", errors, (server, index) => {
|
||||
requireString(server, "id", errors, `contributes.mcpServers[${index}].id`);
|
||||
requireString(server, "displayName", errors, `contributes.mcpServers[${index}].displayName`);
|
||||
requireString(server, "command", errors, `contributes.mcpServers[${index}].command`);
|
||||
if (server.transport !== "stdio") {
|
||||
errors.push(`contributes.mcpServers[${index}].transport must be "stdio"`);
|
||||
}
|
||||
optionalStringArray(server, "args", errors, `contributes.mcpServers[${index}].args`);
|
||||
optionalStringRecord(server, "env", errors, `contributes.mcpServers[${index}].env`);
|
||||
optionalString(server, "cwd", errors, `contributes.mcpServers[${index}].cwd`);
|
||||
optionalBoolean(server, "autoStart", errors, `contributes.mcpServers[${index}].autoStart`);
|
||||
optionalBoolean(server, "allowAbsoluteCommand", errors, `contributes.mcpServers[${index}].allowAbsoluteCommand`);
|
||||
});
|
||||
}
|
||||
|
||||
function validateArray(
|
||||
record: Record<string, unknown>,
|
||||
key: string,
|
||||
errors: string[],
|
||||
validateItem: (item: Record<string, unknown>, index: number) => void
|
||||
): void {
|
||||
const value = record[key];
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(value)) {
|
||||
errors.push(`contributes.${key} must be an array when provided`);
|
||||
return;
|
||||
}
|
||||
|
||||
value.forEach((item, index) => {
|
||||
if (!isRecord(item)) {
|
||||
errors.push(`contributes.${key}[${index}] must be an object`);
|
||||
return;
|
||||
}
|
||||
|
||||
validateItem(item, index);
|
||||
});
|
||||
}
|
||||
|
||||
function requireString(
|
||||
record: Record<string, unknown>,
|
||||
key: string,
|
||||
errors: string[],
|
||||
label = key
|
||||
): void {
|
||||
if (typeof record[key] !== "string" || record[key].trim().length === 0) {
|
||||
errors.push(`${label} must be a non-empty string`);
|
||||
}
|
||||
}
|
||||
|
||||
function optionalString(
|
||||
record: Record<string, unknown>,
|
||||
key: string,
|
||||
errors: string[],
|
||||
label = key
|
||||
): void {
|
||||
if (record[key] !== undefined && typeof record[key] !== "string") {
|
||||
errors.push(`${label} must be a string when provided`);
|
||||
}
|
||||
}
|
||||
|
||||
function optionalNumber(
|
||||
record: Record<string, unknown>,
|
||||
key: string,
|
||||
errors: string[],
|
||||
label = key
|
||||
): void {
|
||||
if (record[key] !== undefined && typeof record[key] !== "number") {
|
||||
errors.push(`${label} must be a number when provided`);
|
||||
}
|
||||
}
|
||||
|
||||
function optionalBoolean(
|
||||
record: Record<string, unknown>,
|
||||
key: string,
|
||||
errors: string[],
|
||||
label = key
|
||||
): void {
|
||||
if (record[key] !== undefined && typeof record[key] !== "boolean") {
|
||||
errors.push(`${label} must be a boolean when provided`);
|
||||
}
|
||||
}
|
||||
|
||||
function optionalStringArray(
|
||||
record: Record<string, unknown>,
|
||||
key: string,
|
||||
errors: string[],
|
||||
label = key
|
||||
): void {
|
||||
const value = record[key];
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
||||
errors.push(`${label} must be an array of strings when provided`);
|
||||
}
|
||||
}
|
||||
|
||||
function optionalStringRecord(
|
||||
record: Record<string, unknown>,
|
||||
key: string,
|
||||
errors: string[],
|
||||
label = key
|
||||
): void {
|
||||
const value = record[key];
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isRecord(value) || Object.values(value).some((item) => typeof item !== "string")) {
|
||||
errors.push(`${label} must be an object of strings when provided`);
|
||||
}
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
1
sdk/IdeaSDK/src/runtime.js
Normal file
1
sdk/IdeaSDK/src/runtime.js
Normal file
@ -0,0 +1 @@
|
||||
export {};
|
||||
36
sdk/IdeaSDK/src/runtime.ts
Normal file
36
sdk/IdeaSDK/src/runtime.ts
Normal file
@ -0,0 +1,36 @@
|
||||
export interface ActivateContext {
|
||||
pluginId: string;
|
||||
logger: PluginLogger;
|
||||
subscriptions: CommandDisposable[];
|
||||
commands?: CommandRegistry;
|
||||
storage?: PluginStorage;
|
||||
}
|
||||
|
||||
export interface IdeAPluginModule {
|
||||
activate(ctx: ActivateContext): void | Promise<void>;
|
||||
deactivate?(): void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface PluginLogger {
|
||||
debug(message: string, ...args: unknown[]): void;
|
||||
info(message: string, ...args: unknown[]): void;
|
||||
warn(message: string, ...args: unknown[]): void;
|
||||
error(message: string, ...args: unknown[]): void;
|
||||
}
|
||||
|
||||
export type CommandHandler = (...args: unknown[]) => unknown | Promise<unknown>;
|
||||
|
||||
export interface CommandRegistry {
|
||||
registerCommand(commandId: string, handler: CommandHandler): CommandDisposable;
|
||||
}
|
||||
|
||||
export interface CommandDisposable {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface PluginStorage {
|
||||
get<T = unknown>(key: string): Promise<T | undefined>;
|
||||
set<T = unknown>(key: string, value: T): Promise<void>;
|
||||
delete(key: string): Promise<void>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user