/** * Plugin Registry Service * * Manages loading, searching, and accessing the curated plugin registry. * Supports both the internal curated registry and user custom registries. * * Key behaviors: * - Bundled registry contains curated plugin entries (read-only) * - User registry can add new entries or override bundled ones * - User entries take precedence over bundled entries with same ID * - Supports both v1 (single file) and v2 (directory) formats * * @since v1.7.0 - Initial registry support * @updated v1.54.0 - Added directory-based registry format (v2) * @updated v2.2.4 - API-first loading with bundled fallback */ import type { PluginCategory, RegistryMetadata, RegistryPlugin, RegistrySearchResult } from './registry-types.js'; /** * Plugin Registry Service */ export declare class RegistryService { private registry; private metadata; private registryPath; private registryDir; private userRegistryPath; private pluginMap; private tagIndex; private usingDirectoryFormat; private useDefaultPaths; private readonly apiUrl; private explicitRegistryPath; private explicitRegistryDir; constructor(registryPath?: string, registryDir?: string, userRegistryPath?: string); /** * Load the registry from disk (internal + user custom). * * - If explicit registryPath provided: use file format * - If explicit registryDir provided: use directory format * - If neither (defaults): try directory first, fall back to file * * @returns true if a registry was found and loaded; false if no registry exists at the configured location (data absence, not error). * @throws ConfigError if the registry exists but cannot be parsed (corrupt JSON, permission denied, schema mismatch). */ load(): boolean; /** * Same as {@link load} but never throws — logs and returns false on any error. * Use at app startup paths where a corrupt registry should not crash the UI. */ tryLoad(): boolean; /** * Load registry with API-first strategy, falling back to bundled data. * Tries GET /v2/pluginator/registry, on any failure falls back to {@link load}. * User custom registry is always merged on top regardless of source. * * @returns true if loaded successfully (API or disk); false if no registry exists. * @throws ConfigError if disk fallback finds a corrupt registry. API fetch failures are non-fatal. */ loadWithApi(): Promise; /** * Same as {@link loadWithApi} but never throws — logs and returns false on any error. * Use at app startup paths where a corrupt registry should not crash the UI. */ tryLoadWithApi(): Promise; /** * Fetch registry data from the NinSys API. * Returns true if successful, false on any error. */ private fetchFromApi; /** * Load registry from directory format (v2). * * @returns true if the directory + metadata exist and were loaded; false if absent. * @throws ConfigError if metadata is corrupt (parse failure, permission denied). * Per-plugin parse failures are logged and skipped (not all plugins must be valid). */ private loadFromDirectory; /** * Validate that a plugin has all required fields */ private isValidPlugin; /** * Load registry from single file format (v1). * * @returns true if the file exists and was loaded; false if absent. * @throws ConfigError if the file exists but cannot be parsed. */ private loadFromFile; /** * Load a single plugin by ID (for lazy loading) * Only works with directory format */ loadPlugin(id: string): RegistryPlugin | null; /** * Get all plugin IDs without loading full plugin data * Useful for listing available plugins */ getAllPluginIds(): string[]; /** * Get registry metadata */ getMetadata(): RegistryMetadata | null; /** * Check if using directory format */ isDirectoryFormat(): boolean; /** * Load user custom registry and merge with internal registry. * User plugins override internal plugins with the same ID. * * Uses Zod schema validation per Phase 3 audit (P0 #8) — rejects entries * with oversized strings (length-capped) before they enter the indexing * path. Invalid entries are skipped with a warning, never crash the load. */ private loadUserRegistry; /** * Build search indexes from loaded registry */ private buildIndexes; /** * Check if registry is loaded */ isLoaded(): boolean; /** * Get registry info */ getInfo(): { version: string; lastUpdated: string; pluginCount: number; format?: 'directory'; } | null; /** * Get plugin by ID */ getById(id: string): RegistryPlugin | undefined; /** * Get plugin by name (case-insensitive) */ getByName(name: string): RegistryPlugin | undefined; /** * Search for plugins in the registry */ search(query: string, limit?: number): RegistrySearchResult[]; /** * Calculate match score for a plugin against a query */ private calculateMatchScore; /** * Get all plugins in a category */ getByCategory(category: PluginCategory): RegistryPlugin[]; /** * Get all plugins with a specific tag */ getByTag(tag: string): RegistryPlugin[]; /** * Get popular plugins */ getPopular(limit?: number): RegistryPlugin[]; /** * Get verified plugins */ getVerified(limit?: number): RegistryPlugin[]; /** * Get plugins compatible with a specific Minecraft version * Compares the version against each plugin's min/max range */ getCompatibleWith(mcVersion: string): RegistryPlugin[]; /** * Check if a version is within a min/max range * Uses simple string comparison for MC versions (1.18 < 1.19 < 1.20) * * Year-versioning tolerance (June 2026 sweep): Minecraft moved to * year-based versions — 26.1 succeeds the 1.21.x line. Registry * `minecraftVersions.max` bounds like "1.21" are DATA recorded before the * scheme change; they say "current as of the 1.x era", not "incompatible * with 26.x". When the queried version is a year version and the recorded * max reaches the end of the legacy line (>= 1.21), the upper bound is * unknown → tolerate (enforce only the min bound). A max that stopped * well before the scheme change (e.g. permissionsex at 1.12) is a real * historical ceiling and still excludes. The data is left untouched — * only this comparator is tolerant. */ private isVersionInRange; /** * Get plugins similar to a given plugin * Based on shared tags and category */ getSimilar(plugin: RegistryPlugin, limit?: number): RegistryPlugin[]; /** * Get registry plugins that match installed plugin names * Useful for showing which installed plugins are from the curated registry */ getInstalledFromRegistry(installedNames: Set): RegistryPlugin[]; /** * Get all unique tags from the registry */ getAllTags(): string[]; /** * Get tags with their plugin counts */ getTagCounts(): Map; /** * Get all plugins */ getAll(): RegistryPlugin[]; /** * Get all plugins sorted alphabetically by name */ getAllSorted(): RegistryPlugin[]; /** * Sort plugins alphabetically by name (case-insensitive) */ sortPluginsByName(plugins: RegistryPlugin[]): RegistryPlugin[]; /** * Sort the internal registry in place by name * Useful for maintaining the registry file */ sortRegistryByName(): void; /** * Save the registry to a file (for maintenance/tooling) * @param filePath Optional file path, defaults to the loaded registry path */ saveRegistry(filePath?: string): boolean; /** * Get total count */ getCount(): number; /** * Get all categories with counts */ getCategoryCounts(): Map; /** * Check if a plugin exists in the registry */ has(idOrName: string): boolean; /** * Get the preferred source for a registry plugin */ getPreferredSource(plugin: RegistryPlugin): RegistryPlugin['sources'][0] | undefined; } /** * Get the singleton registry service */ export declare function getRegistryService(registryPath?: string): RegistryService; /** * Create a new registry service (for testing) */ export declare function createRegistryService(registryPath?: string, userRegistryPath?: string, registryDir?: string): RegistryService; //# sourceMappingURL=registry-service.d.ts.map