/** * Update Check Workflow * * Checks plugins for available updates across configured sources. * Uses the unified cache system for offline resilience. */ import type { PluginManager } from '../core/plugin-manager.js'; import type { RegistryPlugin, RegistryPluginSource, SupersededByAdvisory } from '../core/registry/registry-types.js'; import type { SourceRegistry } from '../core/sources/index.js'; import type { Plugin, PluginUpdateInfo, ScannedPlugin } from '../core/types/index.js'; /** * Update check progress event */ export interface UpdateCheckProgress { current: number; total: number; pluginName: string; status: 'checking' | 'done' | 'error' | 'cached' | 'timeout'; message?: string; } /** * Information about a manual/paid plugin with an available update. * * Premium is a first-class ASSISTED state (roadmap 3.1): every entry carries * enough context for "one step from done" — a deep-link to the page where the * user can download, WHY the download is manual, and the changelog link. */ export interface ManualUpdateInfo { pluginName: string; currentVersion: string; latestVersion: string; /** * Deep-link for the manual download. Resolved via {@link resolveManualPageUrl} * — populated whenever ANY identifier exists (plugin fields, the answering * source's sourceType/sourceId, the source-reported website, or the * changelog page as last resort). */ pageUrl?: string; /** * Why the download is manual: 'premium' (paid resource — version checks * work, artifact sits behind the buyer's account) or 'external-page' (the * source redirects to an HTML page, not an artifact). Mirrored from * `latestInfo.manualDownloadReason`; absent for registry-flagged manual * sources checked without runtime detection. */ reason?: 'premium' | 'external-page'; /** Changelog link when the source provides one (SpigotSource always does). */ changelogUrl?: string; supersededBy?: SupersededByAdvisory; } /** * Update check result */ export interface UpdateCheckResult { updatesAvailable: PluginUpdateInfo[]; upToDate: PluginUpdateInfo[]; errors: Array<{ pluginName: string; error: string; }>; skipped: Array<{ pluginName: string; reason: string; }>; manualUpdates: ManualUpdateInfo[]; ignored: string[]; fromCache: number; staleCacheCount: number; duration: number; } /** * Update check options */ export interface UpdateCheckOptions { enabledOnly?: boolean; sourceType?: string; githubToken?: string; useCache?: boolean; forceRefresh?: boolean; timeout?: number; ignoredPlugins?: string[]; onProgress?: (progress: UpdateCheckProgress) => void; } /** * The subset of RegistryService the update checker consumes for multi-source * fallback. Narrow so tests can inject fakes without constructing the full * service (which reads bundled + user registry files). */ export interface RegistryLookup { isLoaded(): boolean; tryLoad(): boolean; getById(id: string): RegistryPlugin | undefined; } /** * Map a registry source entry to the identifier `SourceRegistry.checkUpdate` * expects for that source type. Mirrors the auto-assigner's * `mapSourceIdentifiers` → `getSourceIdentifier` chain, but flattened for * direct check-time use (June 9 2026 audit: registry entries carry 2-3 * sources but only the preferred one was ever consulted). * * @returns The check identifier, or null when the entry lacks the field its * type requires (caller skips that source). */ export declare function getRegistrySourceIdentifier(source: RegistryPluginSource): string | null; /** * Resolve the deep-link for a manual/premium update (roadmap 3.1: premium is * ASSISTED, so every manual entry must point somewhere actionable). * * Resolution order: * 1. `latestInfo.website` for 'external-page' results — the source already * told us exactly where the artifact lives (e.g. Spiget's externalUrl). * 2. A page derived from the ANSWERING source's sourceType/sourceId. This * outranks the plugin's own configured page: on registry-fallback rescues * the configured source FAILED the check (that's why fallback ran) — the * store that actually answered is where the artifact lives. For normal * checks the answering source IS the configured source, so the order is * indistinguishable. * 3. The plugin's own store page via {@link getPluginPageUrl}. * 4. `latestInfo.website`, then `latestInfo.changelogUrl` as last resorts. */ export declare function resolveManualPageUrl(plugin: Plugin, info: PluginUpdateInfo): string | undefined; /** * Update checker class * * Server-platform selection (W3 groundwork: Velocity/BungeeCord/Folia) is * baked into the source instances at factory time — see * `SourceCreationOptions.serverPlatform` in cli-source-helpers.ts. The * checker itself stays platform-agnostic; it simply calls whatever sources * the injected SourceRegistry carries. */ export declare class UpdateChecker { private pluginManager; private sourceRegistry; private registryService?; constructor(pluginManager: PluginManager, sourceRegistry: SourceRegistry, registryService?: RegistryLookup | undefined); /** * Dispatch a progress event via the callback */ private reportProgress; /** * Route a successful check result into the right bucket. Runtime-detected * manual results (premium wall / external HTML page — no downloadUrl, a * `manualDownloadReason` on latestInfo) land in `manualUpdates` even when * the plugin is NOT flagged `manualDownload` in config: premium is a * first-class ASSISTED state, not a broken download row (roadmap 3.1). * Applies uniformly to fresh, cached, and stale-cache reads — the cache * mapper preserves `manualDownloadReason` so routing survives round-trips. */ private recordCheckedUpdate; /** * Build an enriched {@link ManualUpdateInfo} from a check result: deep-link * (always resolved when any identifier exists — see * {@link resolveManualPageUrl}), the manual reason, changelog link, and the * registry's EOL advisory. */ private toManualUpdateInfo; /** * Check all configured plugins for updates AND persist results to the * shared update-results cache (`cache.mergeUpdateResults` at the end of * this method — merge-by-plugin, so entries for plugins not checked this * run survive). The "AndCache" suffix flags this side effect at every * call site — earlier `checkAll` name made the persistence invisible * and led to a redundant save loop in useUpdates.ts (removed v2.11.3). * * Use `forceRefresh: true` in options to bypass the read-side cache * lookup but still write fresh results. */ checkAllAndCache(options?: UpdateCheckOptions): Promise; /** * Check if we have a fresh cached result for a plugin */ private checkCachedPlugin; /** * Get stale cached result for fallback (when API fails) */ private getStaleCache; /** * Look up the plugin's matched registry entry and return its EOL advisory, * if any (roadmap 3.6). The cached-result mapper intentionally does not * persist the advisory — it is registry-derived and recomputed per check so * registry updates take effect immediately. */ private getSupersededAdvisory; /** * Mutating convenience for the cached-read paths: attach the registry's * `supersededBy` advisory to a cache-restored result (cache entries never * carry it — see {@link getSupersededAdvisory}). */ private attachSupersededAdvisory; /** * Check a single plugin for updates. * * When the preferred-source check fails (not-found/error) and the plugin is * matched to a registry entry, the entry's other sources are tried in order * (see {@link tryRegistryFallback}). The plugin's assignment is NOT mutated * — the fallback only rescues the check (Phase 2 handles propagation). * * When the matched registry entry carries a `supersededBy` EOL advisory, * it is attached to the result (roadmap 3.6: abandoned plugins must not * read as plain "up to date" forever). */ checkPlugin(plugin: Plugin): Promise; /** * Source-check half of {@link checkPlugin} — everything except the * superseded-by advisory attach, which applies uniformly to all return * paths (success, fallback rescue, and error results). */ private checkPluginViaSources; /** * Try the registry entry's other sources after the preferred check failed. * * Registry entries carry 2-3 sources in priority order, but before June 9 * 2026 only the preferred one was ever consulted — one 404/rate-limit/ * premium wall meant total failure for that plugin. Sources are tried in * registry order, skipping the type that already failed and any type with * no registered source instance. The first success wins; its source type is * recorded on `latestInfo.sourceType`. * * Honors per-source `manualDownload`: if the succeeding registry source is * premium/manual, the result's downloadUrl is blanked so callers can't * auto-download an artifact that sits behind a payment wall. * * @returns The first successful fallback result, or null when no fallback * exists/succeeds (caller preserves the original error). */ private tryRegistryFallback; /** * Resolve the registry service for fallback lookups: the injected instance * (tests) or the lazily-loaded singleton (production). Returns null when no * registry can be loaded — fallback is then silently unavailable. */ private resolveRegistryService; /** * Check a scanned plugin against configured sources */ checkScannedPlugin(scannedPlugin: ScannedPlugin, configuredPlugin?: Plugin): Promise; /** * Compare two versions and return if update is available */ hasUpdate(currentVersion: string, latestVersion: string): boolean; /** * Get the last update check timestamp */ getLastCheckTime(): Promise; /** * Check if update cache is still fresh */ isCacheFresh(): Promise; /** * Clear the update results cache */ clearCache(): Promise; } /** * Create an update checker instance */ export declare function createUpdateChecker(pluginManager: PluginManager, sourceRegistry: SourceRegistry, registryService?: RegistryLookup): UpdateChecker; /** * Quick check for updates on a list of plugins */ export declare function quickUpdateCheck(plugins: Plugin[], sourceRegistry: SourceRegistry, getVersion: (name: string) => string | undefined): Promise; //# sourceMappingURL=update-check.d.ts.map