feat: Implement Plugin Marketplace Manager and Client
- Added PluginMarketplaceManager to coordinate marketplace reads and manage installed plugins. - Introduced PluginMarketplaceClient for fetching marketplace metadata. - Implemented storage management for installed plugins using MemoryStorage. - Added methods for installing, uninstalling, and enabling/disabling plugins. - Created type definitions for PluginMarketplaceManager options and installed plugin records. - Added support for fetching full marketplace catalog and individual plugin metadata. - Included error handling for fetch operations and validation of plugin data. - Updated index.browser.ts to export new modules and types.
This commit is contained in:
53
dist/PluginMarketplaceClient.d.ts
vendored
Normal file
53
dist/PluginMarketplaceClient.d.ts
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
import type { PluginIndex, PluginMetadata } from './types.js';
|
||||
/**
|
||||
* Minimal response shape required by marketplace client.
|
||||
*/
|
||||
export interface PluginMarketplaceResponse {
|
||||
ok: boolean;
|
||||
status: number;
|
||||
json(): Promise<unknown>;
|
||||
}
|
||||
/**
|
||||
* Fetch function used by marketplace client.
|
||||
*/
|
||||
export type PluginMarketplaceFetch = (input: string, init?: Record<string, unknown>) => Promise<PluginMarketplaceResponse>;
|
||||
/**
|
||||
* Construction options for {@link PluginMarketplaceClient}.
|
||||
*/
|
||||
export interface PluginMarketplaceClientOptions {
|
||||
/** URL of marketplace index JSON file. */
|
||||
indexUrl: string;
|
||||
/** Base URL used to resolve `${pluginId}.json` metadata files. */
|
||||
baseUrl: string;
|
||||
/** Optional fetch implementation. Falls back to global `fetch`. */
|
||||
fetchFn?: PluginMarketplaceFetch;
|
||||
}
|
||||
/**
|
||||
* Host-agnostic client for reading marketplace metadata.
|
||||
*/
|
||||
export declare class PluginMarketplaceClient {
|
||||
private readonly indexUrl;
|
||||
private readonly baseUrl;
|
||||
private readonly fetchFn?;
|
||||
constructor(options: PluginMarketplaceClientOptions);
|
||||
/**
|
||||
* Fetches and validates marketplace index.
|
||||
*/
|
||||
fetchIndex(): Promise<PluginIndex>;
|
||||
/**
|
||||
* Fetches and validates single plugin metadata entry.
|
||||
*
|
||||
* @param pluginId - Identifier referenced by marketplace index.
|
||||
*/
|
||||
fetchPlugin(pluginId: string): Promise<PluginMetadata>;
|
||||
/**
|
||||
* Fetches full marketplace catalog.
|
||||
*
|
||||
* Entries that fail fetch or validation get skipped so host can still render
|
||||
* rest of catalog.
|
||||
*/
|
||||
fetchPlugins(): Promise<PluginMetadata[]>;
|
||||
private buildMetadataUrl;
|
||||
private getFetchFn;
|
||||
}
|
||||
//# sourceMappingURL=PluginMarketplaceClient.d.ts.map
|
||||
1
dist/PluginMarketplaceClient.d.ts.map
vendored
Normal file
1
dist/PluginMarketplaceClient.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"PluginMarketplaceClient.d.ts","sourceRoot":"","sources":["../src/PluginMarketplaceClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,CACnC,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC3B,OAAO,CAAC,yBAAyB,CAAC,CAAC;AAExC;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC7C,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IAEjB,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAEhB,mEAAmE;IACnE,OAAO,CAAC,EAAE,sBAAsB,CAAC;CAClC;AAED;;GAEG;AACH,qBAAa,uBAAuB;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAyB;gBAEtC,OAAO,EAAE,8BAA8B;IAMnD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,WAAW,CAAC;IAUxC;;;;OAIG;IACG,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAU5D;;;;;OAKG;IACG,YAAY,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAO/C,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,UAAU;CAWnB"}
|
||||
94
dist/PluginMarketplaceClient.js
vendored
Normal file
94
dist/PluginMarketplaceClient.js
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Host-agnostic client for reading marketplace metadata.
|
||||
*/
|
||||
export class PluginMarketplaceClient {
|
||||
constructor(options) {
|
||||
this.indexUrl = options.indexUrl;
|
||||
this.baseUrl = options.baseUrl;
|
||||
this.fetchFn = options.fetchFn;
|
||||
}
|
||||
/**
|
||||
* Fetches and validates marketplace index.
|
||||
*/
|
||||
async fetchIndex() {
|
||||
const response = await this.getFetchFn()(this.indexUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch plugin index: HTTP ${response.status}`);
|
||||
}
|
||||
return normalizePluginIndex(await response.json());
|
||||
}
|
||||
/**
|
||||
* Fetches and validates single plugin metadata entry.
|
||||
*
|
||||
* @param pluginId - Identifier referenced by marketplace index.
|
||||
*/
|
||||
async fetchPlugin(pluginId) {
|
||||
const response = await this.getFetchFn()(this.buildMetadataUrl(pluginId));
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch plugin ${pluginId}: HTTP ${response.status}`);
|
||||
}
|
||||
return normalizePluginMetadata(await response.json(), pluginId);
|
||||
}
|
||||
/**
|
||||
* Fetches full marketplace catalog.
|
||||
*
|
||||
* Entries that fail fetch or validation get skipped so host can still render
|
||||
* rest of catalog.
|
||||
*/
|
||||
async fetchPlugins() {
|
||||
const index = await this.fetchIndex();
|
||||
const results = await Promise.allSettled(index.plugins.map((pluginId) => this.fetchPlugin(pluginId)));
|
||||
return results.flatMap((result) => (result.status === 'fulfilled' ? [result.value] : []));
|
||||
}
|
||||
buildMetadataUrl(pluginId) {
|
||||
const trimmedBaseUrl = this.baseUrl.endsWith('/') ? this.baseUrl : `${this.baseUrl}/`;
|
||||
return `${trimmedBaseUrl}${pluginId}.json`;
|
||||
}
|
||||
getFetchFn() {
|
||||
if (this.fetchFn) {
|
||||
return this.fetchFn;
|
||||
}
|
||||
if (typeof fetch !== 'function') {
|
||||
throw new Error('No fetch implementation available for PluginMarketplaceClient');
|
||||
}
|
||||
return fetch;
|
||||
}
|
||||
}
|
||||
function normalizePluginIndex(value) {
|
||||
const record = asRecord(value, 'Plugin index payload must be object');
|
||||
return {
|
||||
version: asString(record.version),
|
||||
updatedAt: asString(record.updatedAt),
|
||||
plugins: Array.isArray(record.plugins)
|
||||
? record.plugins.filter((entry) => typeof entry === 'string')
|
||||
: [],
|
||||
};
|
||||
}
|
||||
function normalizePluginMetadata(value, fallbackId) {
|
||||
const record = asRecord(value, `Plugin metadata for ${fallbackId} must be object`);
|
||||
return {
|
||||
id: asString(record.id) || fallbackId,
|
||||
name: asString(record.name),
|
||||
version: asString(record.version),
|
||||
description: asString(record.description),
|
||||
author: asString(record.author),
|
||||
repository: asString(record.repository),
|
||||
thumbnail: asString(record.thumbnail),
|
||||
downloadUrl: asString(record.downloadUrl),
|
||||
homepage: asString(record.homepage),
|
||||
tags: Array.isArray(record.tags)
|
||||
? record.tags.filter((entry) => typeof entry === 'string')
|
||||
: [],
|
||||
addedDate: asString(record.addedDate),
|
||||
};
|
||||
}
|
||||
function asRecord(value, message) {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
throw new Error(message);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
function asString(value) {
|
||||
return typeof value === 'string' ? value : '';
|
||||
}
|
||||
//# sourceMappingURL=PluginMarketplaceClient.js.map
|
||||
1
dist/PluginMarketplaceClient.js.map
vendored
Normal file
1
dist/PluginMarketplaceClient.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"PluginMarketplaceClient.js","sourceRoot":"","sources":["../src/PluginMarketplaceClient.ts"],"names":[],"mappings":"AAiCA;;GAEG;AACH,MAAM,OAAO,uBAAuB;IAKlC,YAAY,OAAuC;QACjD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU;QACd,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAExD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,sCAAsC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QAC3E,CAAC;QAED,OAAO,oBAAoB,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACrD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,WAAW,CAAC,QAAgB;QAChC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC;QAE1E,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,0BAA0B,QAAQ,UAAU,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QACjF,CAAC;QAED,OAAO,uBAAuB,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC;IAClE,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,YAAY;QAChB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAEtG,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5F,CAAC;IAEO,gBAAgB,CAAC,QAAgB;QACvC,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,OAAO,GAAG,CAAC;QACtF,OAAO,GAAG,cAAc,GAAG,QAAQ,OAAO,CAAC;IAC7C,CAAC;IAEO,UAAU;QAChB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,IAAI,CAAC,OAAO,CAAC;QACtB,CAAC;QAED,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;QACnF,CAAC;QAED,OAAO,KAA0C,CAAC;IACpD,CAAC;CACF;AAED,SAAS,oBAAoB,CAAC,KAAc;IAC1C,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,qCAAqC,CAAC,CAAC;IAEtE,OAAO;QACL,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC;QACjC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC;QACrC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC;YACpC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC;YAC9E,CAAC,CAAC,EAAE;KACP,CAAC;AACJ,CAAC;AAED,SAAS,uBAAuB,CAAC,KAAc,EAAE,UAAkB;IACjE,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,uBAAuB,UAAU,iBAAiB,CAAC,CAAC;IAEnF,OAAO;QACL,EAAE,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,UAAU;QACrC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC;QAC3B,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC;QACjC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC;QACzC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;QAC/B,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC;QACvC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC;QACrC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC;QACzC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC;QACnC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;YAC9B,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC;YAC3E,CAAC,CAAC,EAAE;QACN,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC;KACtC,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc,EAAE,OAAe;IAC/C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO,KAAgC,CAAC;AAC1C,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAChD,CAAC"}
|
||||
88
dist/PluginMarketplaceManager.d.ts
vendored
Normal file
88
dist/PluginMarketplaceManager.d.ts
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
import { type IPluginStorage } from './interfaces.js';
|
||||
import type { PluginRegistry } from './PluginRegistry.js';
|
||||
import type { InstalledPlugin, PluginMetadata } from './types.js';
|
||||
import { PluginMarketplaceClient } from './PluginMarketplaceClient.js';
|
||||
/**
|
||||
* Host-visible installed-plugin metadata.
|
||||
*
|
||||
* Hosts may only know filesystem facts such as `id`, `name`, or `installedDate`.
|
||||
* Stored marketplace metadata fills remaining fields.
|
||||
*/
|
||||
export type PluginMarketplaceInstalledRecord = Partial<InstalledPlugin> & Pick<InstalledPlugin, 'id'>;
|
||||
/**
|
||||
* Host callbacks used by {@link PluginMarketplaceManager}.
|
||||
*/
|
||||
export interface PluginMarketplaceHostAdapter {
|
||||
/**
|
||||
* Returns host-known installed plugins.
|
||||
*/
|
||||
listInstalledPlugins?(): Promise<PluginMarketplaceInstalledRecord[]>;
|
||||
/**
|
||||
* Performs host-specific install side effects.
|
||||
*/
|
||||
installPlugin?(plugin: PluginMetadata): Promise<void>;
|
||||
/**
|
||||
* Performs host-specific uninstall side effects.
|
||||
*/
|
||||
uninstallPlugin?(pluginId: string): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* Construction options for {@link PluginMarketplaceManager}.
|
||||
*/
|
||||
export interface PluginMarketplaceManagerOptions {
|
||||
/** Shared marketplace client used to query remote metadata. */
|
||||
client: PluginMarketplaceClient;
|
||||
/** Optional persistent storage for installed-plugin state. */
|
||||
storage?: IPluginStorage;
|
||||
/** Optional host adapter for install and discovery side effects. */
|
||||
host?: PluginMarketplaceHostAdapter;
|
||||
/** Optional registry used when disabling or uninstalling loaded plugins. */
|
||||
registry?: PluginRegistry;
|
||||
/** Optional override for installed-plugin storage key. */
|
||||
installedPluginsStorageKey?: string;
|
||||
}
|
||||
/**
|
||||
* Coordinates marketplace reads with host-specific install and enable state.
|
||||
*/
|
||||
export declare class PluginMarketplaceManager {
|
||||
private readonly client;
|
||||
private readonly storage;
|
||||
private readonly host?;
|
||||
private readonly registry?;
|
||||
private readonly installedPluginsStorageKey;
|
||||
constructor(options: PluginMarketplaceManagerOptions);
|
||||
/**
|
||||
* Fetches full marketplace catalog.
|
||||
*/
|
||||
fetchMarketplacePlugins(): Promise<PluginMetadata[]>;
|
||||
/**
|
||||
* Lists installed plugins merged with persisted enabled state.
|
||||
*/
|
||||
listInstalledPlugins(): Promise<InstalledPlugin[]>;
|
||||
/**
|
||||
* Lists installed plugins currently enabled.
|
||||
*/
|
||||
listEnabledPlugins(): Promise<InstalledPlugin[]>;
|
||||
/**
|
||||
* Returns whether plugin is recorded as installed.
|
||||
*/
|
||||
isPluginInstalled(pluginId: string): Promise<boolean>;
|
||||
/**
|
||||
* Installs marketplace plugin via host adapter and persists state.
|
||||
*/
|
||||
installPlugin(plugin: PluginMetadata): Promise<InstalledPlugin>;
|
||||
/**
|
||||
* Removes plugin from host and clears persisted install state.
|
||||
*/
|
||||
uninstallPlugin(pluginId: string): Promise<void>;
|
||||
/**
|
||||
* Enables or disables installed plugin.
|
||||
*
|
||||
* Disabling triggers `registry.unregisterPlugin` when registry exists.
|
||||
* Enabling only updates persisted state; host controls actual load timing.
|
||||
*/
|
||||
setPluginEnabled(pluginId: string, enabled: boolean): Promise<InstalledPlugin | null>;
|
||||
private readStoredInstalledPlugins;
|
||||
private writeStoredInstalledPlugins;
|
||||
}
|
||||
//# sourceMappingURL=PluginMarketplaceManager.d.ts.map
|
||||
1
dist/PluginMarketplaceManager.d.ts.map
vendored
Normal file
1
dist/PluginMarketplaceManager.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"PluginMarketplaceManager.d.ts","sourceRoot":"","sources":["../src/PluginMarketplaceManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAIvE;;;;;GAKG;AACH,MAAM,MAAM,gCAAgC,GAAG,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;AAEtG;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C;;OAEG;IACH,oBAAoB,CAAC,IAAI,OAAO,CAAC,gCAAgC,EAAE,CAAC,CAAC;IAErE;;OAEG;IACH,aAAa,CAAC,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtD;;OAEG;IACH,eAAe,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AAED;;GAEG;AACH,MAAM,WAAW,+BAA+B;IAC9C,+DAA+D;IAC/D,MAAM,EAAE,uBAAuB,CAAC;IAEhC,8DAA8D;IAC9D,OAAO,CAAC,EAAE,cAAc,CAAC;IAEzB,oEAAoE;IACpE,IAAI,CAAC,EAAE,4BAA4B,CAAC;IAEpC,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,cAAc,CAAC;IAE1B,0DAA0D;IAC1D,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AAED;;GAEG;AACH,qBAAa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAiB;IAC3C,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAS;gBAExC,OAAO,EAAE,+BAA+B;IASpD;;OAEG;IACG,uBAAuB,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAI1D;;OAEG;IACG,oBAAoB,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAaxD;;OAEG;IACG,kBAAkB,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAItD;;OAEG;IACG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI3D;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAerE;;OAEG;IACG,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWtD;;;;;OAKG;IACG,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAiB3F,OAAO,CAAC,0BAA0B;IAsBlC,OAAO,CAAC,2BAA2B;CAGpC"}
|
||||
169
dist/PluginMarketplaceManager.js
vendored
Normal file
169
dist/PluginMarketplaceManager.js
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
import { MemoryStorage } from './interfaces.js';
|
||||
const DEFAULT_INSTALLED_PLUGINS_STORAGE_KEY = 'plugin-manager:installed-plugins';
|
||||
/**
|
||||
* Coordinates marketplace reads with host-specific install and enable state.
|
||||
*/
|
||||
export class PluginMarketplaceManager {
|
||||
constructor(options) {
|
||||
this.client = options.client;
|
||||
this.storage = options.storage ?? new MemoryStorage();
|
||||
this.host = options.host;
|
||||
this.registry = options.registry;
|
||||
this.installedPluginsStorageKey =
|
||||
options.installedPluginsStorageKey ?? DEFAULT_INSTALLED_PLUGINS_STORAGE_KEY;
|
||||
}
|
||||
/**
|
||||
* Fetches full marketplace catalog.
|
||||
*/
|
||||
async fetchMarketplacePlugins() {
|
||||
return this.client.fetchPlugins();
|
||||
}
|
||||
/**
|
||||
* Lists installed plugins merged with persisted enabled state.
|
||||
*/
|
||||
async listInstalledPlugins() {
|
||||
const storedPlugins = this.readStoredInstalledPlugins();
|
||||
if (!this.host?.listInstalledPlugins) {
|
||||
return storedPlugins;
|
||||
}
|
||||
const hostPlugins = await this.host.listInstalledPlugins();
|
||||
const mergedPlugins = mergeInstalledPlugins(hostPlugins, storedPlugins);
|
||||
this.writeStoredInstalledPlugins(mergedPlugins);
|
||||
return mergedPlugins;
|
||||
}
|
||||
/**
|
||||
* Lists installed plugins currently enabled.
|
||||
*/
|
||||
async listEnabledPlugins() {
|
||||
return (await this.listInstalledPlugins()).filter((plugin) => plugin.enabled);
|
||||
}
|
||||
/**
|
||||
* Returns whether plugin is recorded as installed.
|
||||
*/
|
||||
async isPluginInstalled(pluginId) {
|
||||
return (await this.listInstalledPlugins()).some((plugin) => plugin.id === pluginId);
|
||||
}
|
||||
/**
|
||||
* Installs marketplace plugin via host adapter and persists state.
|
||||
*/
|
||||
async installPlugin(plugin) {
|
||||
await this.host?.installPlugin?.(plugin);
|
||||
const installedPlugin = {
|
||||
...plugin,
|
||||
installedDate: new Date().toISOString(),
|
||||
enabled: true,
|
||||
};
|
||||
const updatedPlugins = upsertInstalledPlugin(this.readStoredInstalledPlugins(), installedPlugin);
|
||||
this.writeStoredInstalledPlugins(updatedPlugins);
|
||||
return updatedPlugins.find((entry) => entry.id === plugin.id) ?? installedPlugin;
|
||||
}
|
||||
/**
|
||||
* Removes plugin from host and clears persisted install state.
|
||||
*/
|
||||
async uninstallPlugin(pluginId) {
|
||||
if (this.registry) {
|
||||
await this.registry.unregisterPlugin(pluginId);
|
||||
}
|
||||
await this.host?.uninstallPlugin?.(pluginId);
|
||||
this.writeStoredInstalledPlugins(this.readStoredInstalledPlugins().filter((plugin) => plugin.id !== pluginId));
|
||||
}
|
||||
/**
|
||||
* Enables or disables installed plugin.
|
||||
*
|
||||
* Disabling triggers `registry.unregisterPlugin` when registry exists.
|
||||
* Enabling only updates persisted state; host controls actual load timing.
|
||||
*/
|
||||
async setPluginEnabled(pluginId, enabled) {
|
||||
if (!enabled && this.registry) {
|
||||
await this.registry.unregisterPlugin(pluginId);
|
||||
}
|
||||
const installedPlugins = await this.listInstalledPlugins();
|
||||
const targetPlugin = installedPlugins.find((plugin) => plugin.id === pluginId);
|
||||
if (!targetPlugin) {
|
||||
return null;
|
||||
}
|
||||
const updatedPlugin = { ...targetPlugin, enabled };
|
||||
this.writeStoredInstalledPlugins(upsertInstalledPlugin(installedPlugins, updatedPlugin));
|
||||
return updatedPlugin;
|
||||
}
|
||||
readStoredInstalledPlugins() {
|
||||
const rawValue = this.storage.getItem(this.installedPluginsStorageKey);
|
||||
if (!rawValue) {
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
const parsedValue = JSON.parse(rawValue);
|
||||
if (!Array.isArray(parsedValue)) {
|
||||
return [];
|
||||
}
|
||||
return parsedValue
|
||||
.map((entry) => normalizeInstalledPlugin(entry))
|
||||
.filter((entry) => entry !== null);
|
||||
}
|
||||
catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
writeStoredInstalledPlugins(plugins) {
|
||||
this.storage.setItem(this.installedPluginsStorageKey, JSON.stringify(plugins));
|
||||
}
|
||||
}
|
||||
function mergeInstalledPlugins(hostPlugins, storedPlugins) {
|
||||
const storedPluginsById = new Map(storedPlugins.map((plugin) => [plugin.id, plugin]));
|
||||
const discoveredAt = new Date().toISOString();
|
||||
return hostPlugins.map((plugin) => {
|
||||
const storedPlugin = storedPluginsById.get(plugin.id);
|
||||
return {
|
||||
id: plugin.id,
|
||||
name: pickString(plugin.name, storedPlugin?.name),
|
||||
version: pickString(plugin.version, storedPlugin?.version),
|
||||
description: pickString(plugin.description, storedPlugin?.description),
|
||||
author: pickString(plugin.author, storedPlugin?.author),
|
||||
repository: pickString(plugin.repository, storedPlugin?.repository),
|
||||
thumbnail: pickString(plugin.thumbnail, storedPlugin?.thumbnail),
|
||||
downloadUrl: pickString(plugin.downloadUrl, storedPlugin?.downloadUrl),
|
||||
homepage: pickString(plugin.homepage, storedPlugin?.homepage),
|
||||
tags: Array.isArray(plugin.tags)
|
||||
? plugin.tags.filter((entry) => typeof entry === 'string')
|
||||
: storedPlugin?.tags ?? [],
|
||||
addedDate: pickString(plugin.addedDate, storedPlugin?.addedDate),
|
||||
installedDate: pickString(plugin.installedDate, storedPlugin?.installedDate) || discoveredAt,
|
||||
enabled: typeof plugin.enabled === 'boolean' ? plugin.enabled : storedPlugin?.enabled ?? true,
|
||||
};
|
||||
});
|
||||
}
|
||||
function upsertInstalledPlugin(plugins, nextPlugin) {
|
||||
const filteredPlugins = plugins.filter((plugin) => plugin.id !== nextPlugin.id);
|
||||
return [...filteredPlugins, nextPlugin];
|
||||
}
|
||||
function normalizeInstalledPlugin(value) {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
return null;
|
||||
}
|
||||
const record = value;
|
||||
const id = typeof record.id === 'string' ? record.id : '';
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
id,
|
||||
name: typeof record.name === 'string' ? record.name : '',
|
||||
version: typeof record.version === 'string' ? record.version : '',
|
||||
description: typeof record.description === 'string' ? record.description : '',
|
||||
author: typeof record.author === 'string' ? record.author : '',
|
||||
repository: typeof record.repository === 'string' ? record.repository : '',
|
||||
thumbnail: typeof record.thumbnail === 'string' ? record.thumbnail : '',
|
||||
downloadUrl: typeof record.downloadUrl === 'string' ? record.downloadUrl : '',
|
||||
homepage: typeof record.homepage === 'string' ? record.homepage : '',
|
||||
tags: Array.isArray(record.tags)
|
||||
? record.tags.filter((entry) => typeof entry === 'string')
|
||||
: [],
|
||||
addedDate: typeof record.addedDate === 'string' ? record.addedDate : '',
|
||||
installedDate: typeof record.installedDate === 'string' ? record.installedDate : '',
|
||||
enabled: typeof record.enabled === 'boolean' ? record.enabled : true,
|
||||
};
|
||||
}
|
||||
function pickString(value, fallback = '') {
|
||||
return typeof value === 'string' && value.length > 0 ? value : fallback;
|
||||
}
|
||||
//# sourceMappingURL=PluginMarketplaceManager.js.map
|
||||
1
dist/PluginMarketplaceManager.js.map
vendored
Normal file
1
dist/PluginMarketplaceManager.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
15
dist/index.browser.d.ts
vendored
Normal file
15
dist/index.browser.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
export type { IPluginStorage, IPluginEventClient, } from './interfaces.js';
|
||||
export { MemoryStorage } from './interfaces.js';
|
||||
export { PluginMarketplaceClient } from './PluginMarketplaceClient.js';
|
||||
export type { PluginMarketplaceClientOptions, PluginMarketplaceFetch, PluginMarketplaceResponse, } from './PluginMarketplaceClient.js';
|
||||
export { PluginMarketplaceManager } from './PluginMarketplaceManager.js';
|
||||
export type { PluginMarketplaceHostAdapter, PluginMarketplaceInstalledRecord, PluginMarketplaceManagerOptions, } from './PluginMarketplaceManager.js';
|
||||
export type { PluginMetadata, PluginIndex, InstalledPlugin, } from './types.js';
|
||||
export { PluginTab } from './types.js';
|
||||
export type { ThemeColorGroup, ThemePaletteGroup, PluginThemeColors, PluginTheme, CommandArg, PluginCommand, MessageInterceptor, MessageContext, CustomRenderer, SettingDefinition, SettingsSchema, NotificationOptions, PluginContext, PluginSettingsSection, UILocation, Plugin, PluginLogEntry, } from './PluginInterfaces.js';
|
||||
export { generateThemeCSS } from './theme-css.js';
|
||||
export { PluginRegistry } from './PluginRegistry.js';
|
||||
export type { PluginRegistryOptions } from './PluginRegistry.js';
|
||||
export { createPluginContext } from './PluginContext.js';
|
||||
export type { PluginContextOptions } from './PluginContext.js';
|
||||
//# sourceMappingURL=index.browser.d.ts.map
|
||||
1
dist/index.browser.d.ts.map
vendored
Normal file
1
dist/index.browser.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.browser.d.ts","sourceRoot":"","sources":["../src/index.browser.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,cAAc,EACd,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,YAAY,EACV,8BAA8B,EAC9B,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,YAAY,EACV,4BAA4B,EAC5B,gCAAgC,EAChC,+BAA+B,GAChC,MAAM,+BAA+B,CAAC;AAEvC,YAAY,EACV,cAAc,EACd,WAAW,EACX,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,qBAAqB,EACrB,UAAU,EACV,MAAM,EACN,cAAc,GACf,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,YAAY,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC"}
|
||||
8
dist/index.browser.js
vendored
Normal file
8
dist/index.browser.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
export { MemoryStorage } from './interfaces.js';
|
||||
export { PluginMarketplaceClient } from './PluginMarketplaceClient.js';
|
||||
export { PluginMarketplaceManager } from './PluginMarketplaceManager.js';
|
||||
export { PluginTab } from './types.js';
|
||||
export { generateThemeCSS } from './theme-css.js';
|
||||
export { PluginRegistry } from './PluginRegistry.js';
|
||||
export { createPluginContext } from './PluginContext.js';
|
||||
//# sourceMappingURL=index.browser.js.map
|
||||
1
dist/index.browser.js.map
vendored
Normal file
1
dist/index.browser.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.browser.js","sourceRoot":"","sources":["../src/index.browser.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAOvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAYzE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAsBvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC"}
|
||||
4
dist/index.d.ts
vendored
4
dist/index.d.ts
vendored
@@ -1,5 +1,9 @@
|
||||
export type { IPluginStorage, IPluginEventClient, } from './interfaces.js';
|
||||
export { MemoryStorage } from './interfaces.js';
|
||||
export { PluginMarketplaceClient } from './PluginMarketplaceClient.js';
|
||||
export type { PluginMarketplaceClientOptions, PluginMarketplaceFetch, PluginMarketplaceResponse, } from './PluginMarketplaceClient.js';
|
||||
export { PluginMarketplaceManager } from './PluginMarketplaceManager.js';
|
||||
export type { PluginMarketplaceHostAdapter, PluginMarketplaceManagerOptions, } from './PluginMarketplaceManager.js';
|
||||
export type { PluginMetadata, PluginIndex, InstalledPlugin, } from './types.js';
|
||||
export { PluginTab } from './types.js';
|
||||
export type { ThemeColorGroup, ThemePaletteGroup, PluginThemeColors, PluginTheme, CommandArg, PluginCommand, MessageInterceptor, MessageContext, CustomRenderer, SettingDefinition, SettingsSchema, NotificationOptions, PluginContext, PluginSettingsSection, UILocation, Plugin, PluginLogEntry, } from './PluginInterfaces.js';
|
||||
|
||||
2
dist/index.d.ts.map
vendored
2
dist/index.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,cAAc,EACd,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,YAAY,EACV,cAAc,EACd,WAAW,EACX,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,qBAAqB,EACrB,UAAU,EACV,MAAM,EACN,cAAc,GACf,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,YAAY,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE/D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC"}
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,cAAc,EACd,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,YAAY,EACV,8BAA8B,EAC9B,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,YAAY,EACV,4BAA4B,EAC5B,+BAA+B,GAChC,MAAM,+BAA+B,CAAC;AAEvC,YAAY,EACV,cAAc,EACd,WAAW,EACX,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,qBAAqB,EACrB,UAAU,EACV,MAAM,EACN,cAAc,GACf,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,YAAY,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE/D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC"}
|
||||
2
dist/index.js
vendored
2
dist/index.js
vendored
@@ -1,4 +1,6 @@
|
||||
export { MemoryStorage } from './interfaces.js';
|
||||
export { PluginMarketplaceClient } from './PluginMarketplaceClient.js';
|
||||
export { PluginMarketplaceManager } from './PluginMarketplaceManager.js';
|
||||
export { PluginTab } from './types.js';
|
||||
export { generateThemeCSS } from './theme-css.js';
|
||||
export { PluginRegistry } from './PluginRegistry.js';
|
||||
|
||||
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAOhD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAsBvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAGzD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAOvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAWzE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAsBvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAGzD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
|
||||
Reference in New Issue
Block a user