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:
2026-04-19 01:03:58 +10:00
parent da18b7dfe5
commit dcc17946ef
22 changed files with 1018 additions and 44 deletions

53
dist/PluginMarketplaceClient.d.ts vendored Normal file
View 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
View 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
View 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
View File

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

88
dist/PluginMarketplaceManager.d.ts vendored Normal file
View 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

View 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
View 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

File diff suppressed because one or more lines are too long

15
dist/index.browser.d.ts vendored Normal file
View 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
View 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
View 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
View 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
View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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"}