feat: add plugin marketplace renderer

This commit is contained in:
2026-08-28 18:41:23 +08:00
parent 8b6824a8ba
commit 97c9ad1aad
27 changed files with 1585 additions and 23 deletions

View File

@@ -88,13 +88,18 @@ export type PublicPluginInstallation = Readonly<{
reason?: string;
}>;
export type PublicPluginLibrary = Readonly<{
library: MarketplaceLibrarySnapshot;
installations: readonly PublicPluginInstallation[];
}>;
/** Main-owned Marketplace facade. It never exposes package roots or manifests. */
export interface CodingPluginMarketplaceService {
readCatalog(input?: CatalogQuery): Promise<CatalogPage>;
readDetail(pluginId: string): Promise<PluginDetail>;
readLibrary(): Promise<MarketplaceLibrarySnapshot>;
acquire(pluginId: string): Promise<MarketplaceLibrarySnapshot>;
remove(pluginId: string): Promise<MarketplaceLibrarySnapshot>;
readLibrary(): Promise<PublicPluginLibrary>;
acquire(pluginId: string): Promise<PublicPluginLibrary>;
remove(pluginId: string): Promise<PublicPluginLibrary>;
install(pluginId: string): Promise<PublicPluginInstallation>;
update(pluginId: string): Promise<PublicPluginInstallation>;
uninstall(pluginId: string): Promise<PublicPluginInstallation>;
@@ -120,6 +125,30 @@ function publicInstallation(snapshot: InstallationSnapshot): PublicPluginInstall
};
}
async function publicLibrary(
library: MarketplaceLibrarySnapshot,
packageStore: Pick<PluginPackageStore, 'readInstalledIndex'>,
): Promise<PublicPluginLibrary> {
const libraryPluginIds = new Set(library.items.map(({ pluginId }) => pluginId));
const latest = new Map<string, Awaited<ReturnType<PluginPackageStore['readInstalledIndex']>>[number]>();
for (const record of await packageStore.readInstalledIndex()) {
if (!libraryPluginIds.has(record.pluginId)) continue;
const current = latest.get(record.pluginId);
if (!current || record.installedAt >= current.installedAt) latest.set(record.pluginId, record);
}
return {
library,
installations: [...latest.values()]
.sort((left, right) => left.pluginId.localeCompare(right.pluginId))
.map((record) => ({
status: 'installed',
pluginId: record.pluginId,
releaseId: record.releaseId,
version: record.version,
})),
};
}
export function createCodingPluginMarketplaceService(
options: CreateCodingPluginMarketplaceServiceOptions,
): CodingPluginMarketplaceService {
@@ -140,16 +169,16 @@ export function createCodingPluginMarketplaceService(
return {
readCatalog: (input = {}) => options.marketplace.readCatalog(input),
readDetail: (pluginId) => options.marketplace.readDetail(pluginId),
readLibrary: () => options.marketplace.readLibrary(),
readLibrary: async () => publicLibrary(await options.marketplace.readLibrary(), options.packageStore),
acquire: async (pluginId) => {
const snapshot = await options.marketplace.acquire(pluginId);
await options.onChanged?.({ pluginId, kind: 'acquire' });
return snapshot;
return publicLibrary(snapshot, options.packageStore);
},
remove: async (pluginId) => {
const snapshot = await options.marketplace.remove(pluginId);
await options.onChanged?.({ pluginId, kind: 'remove' });
return snapshot;
return publicLibrary(snapshot, options.packageStore);
},
install: (pluginId) => install(pluginId, 'install'),
update: (pluginId) => install(pluginId, 'update'),
@@ -185,6 +214,7 @@ export interface CodingPluginProjectProjection {
durableProjectId: string | null;
};
policyStatus: PluginPolicyAvailability;
unknownPluginIds: string[];
items: Array<{
id: string;
version: string;
@@ -470,6 +500,9 @@ export function createCodingProjectPluginService(
schemaVersion: 1,
project: { localProjectId: localProject.id, durableProjectId },
policyStatus: policy.status,
unknownPluginIds: [...new Set(selection.filter((id) => (
!pluginDefinitions.some((definition) => definition.id === id)
)))].sort(),
items,
};
}