/** * Manual-Drop Completion Workflow (Phase 3 roadmap 3.2) * * The premium UX keystone: the user clicks a pending update's deep-link and * downloads the artifact in their browser like normal; this workflow then * matches the downloaded file to the pending premium update and installs it * through the SAME validated path as automatic downloads (integrity gate, * backup/rename semantics, plugins-config update, provenance record). * One browser click instead of seven manual steps — zero ToS exposure. * * ON-DEMAND by design: invoked explicitly (`pluginator complete` / the * 'Complete Manual Update' command), it scans candidate files when asked. * No filesystem watcher daemon — that is Phase 4 territory. */ import type { ScannedPlugin } from '../core/types/index.js'; import { type InstallApplyStatus } from './download.js'; /** Candidate freshness window: files modified within the last 24 hours */ export declare const DEFAULT_CANDIDATE_MAX_AGE_MS: number; /** * A pending premium/manual update the user is expected to complete by hand. * Derived from the latest update check's ASSISTED bucket via the shared * update-results cache, so it works offline (see {@link loadPendingExpectations}). */ export interface PendingManualExpectation { pluginName: string; currentVersion: string; /** The version the update check reported as available */ expectedVersion: string; /** Why the download was manual ('premium' | 'external-page'), when known */ reason?: 'premium' | 'external-page'; /** Source type from the plugin's assignment (feeds the provenance record) */ source: string; sourceId?: string; } /** A downloaded file matched to a pending expectation, ready to install. */ export interface ManualDropMatch { expectation: PendingManualExpectation; /** The file the user downloaded (.jar, or the .zip it came in) */ candidatePath: string; /** The actual .jar to install (== candidatePath, or temp-extracted from a zip) */ jarPath: string; /** plugin.yml version of the candidate (equal-or-newer than expectedVersion) */ candidateVersion: string; fromZip: boolean; modifiedAtMs: number; } export interface InstalledManualUpdate { pluginName: string; version: string; filePath: string; /** * How the JAR was applied (Phase 4 safe-apply): 'installed' (swapped into * plugins/) or 'staged-for-restart' (target server was running — the JAR * sits in the update folder and applies on the next restart). Consumers * must say "applies on next restart" for staged entries, never "installed". */ status: InstallApplyStatus; /** Non-blocking compat-gate findings carried from the install (Phase 4). */ warnings?: string[]; } export interface FailedManualUpdate { pluginName: string; candidatePath: string; error: string; } /** A candidate file that did not complete any pending expectation, with the honest reason. */ export interface UnmatchedCandidate { path: string; reason: string; } export interface CompleteManualResult { /** Pending expectations that were loaded/considered */ expectations: PendingManualExpectation[]; /** Candidate→expectation matches found (installed unless dryRun) */ matches: ManualDropMatch[]; installed: InstalledManualUpdate[]; failed: FailedManualUpdate[]; /** Candidates that matched nothing — never silently dropped */ unmatched: UnmatchedCandidate[]; /** Expectations still waiting for a downloaded file */ pending: PendingManualExpectation[]; dryRun: boolean; } export interface CompleteManualOptions { /** Install destination — the test server's plugins dir (the active workspace) */ targetDir: string; /** Explicit candidate file (.jar/.zip). When omitted, the downloads dir is scanned. */ file?: string; /** Directory scanned for candidates (default: config DOWNLOADS_DIR, else ~/Downloads) */ downloadsDir?: string; /** Match + report without installing */ dryRun?: boolean; /** Candidate freshness window in ms (default 24h); explicit `file` is exempt */ maxAgeMs?: number; /** Clock override for tests */ nowMs?: number; /** Injected expectations (tests / callers that already hold a check result) */ expectations?: PendingManualExpectation[]; backupExisting?: boolean; backupDir?: string; /** * Running-state probe for the target server (Phase 4 safe-apply). Default: * {@link createRunningServerProbe} on `targetDir`. When true, updates to * already-installed plugins are STAGED into the update folder. */ isTargetServerRunning?: () => Promise; /** Compat-gate fallback MC version (see download.ts DownloadWorkflowOptions). */ serverMcVersion?: string; /** Host JVM feature release for the Java class-version gate. */ hostJavaMajor?: number; /** Escalate newer-MC-major mismatches from 'warn' to 'block'. */ strictCompat?: boolean; } /** Install-time options shared by the dialog path and the batch workflow. */ export interface InstallManualDropOptions { targetDir: string; backupExisting?: boolean; backupDir?: string; /** See {@link CompleteManualOptions.isTargetServerRunning}. */ isTargetServerRunning?: () => Promise; serverMcVersion?: string; hostJavaMajor?: number; strictCompat?: boolean; } /** * Derive pending manual expectations from the shared update-results cache — * the persisted half of UpdateChecker.checkAllAndCache()'s `manualUpdates` * bucket. An entry is pending when it has an update AND no downloadable * artifact (runtime premium/external detection sets `manualDownloadReason` * and blanks `downloadUrl`; registry no-artifact rows just lack the URL). * Stale cache entries are intentionally accepted: completion is exactly the * offline follow-up to an earlier check. */ export declare function loadPendingExpectations(): Promise; /** * Resolve the directory scanned for manually downloaded files: * explicit override → config DOWNLOADS_DIR → ~/Downloads (cross-platform: * macOS, Linux, and Windows all default the browser download folder there). */ export declare function resolveDownloadsDir(configuredDir?: string): string; /** * Extract the single .jar from a store-packaging .zip (common premium * delivery shape) into a fresh temp directory, bounded by the zip-bomb caps. * * @returns The extracted jar path, or an error string when the zip does not * contain exactly one .jar / trips a cap. */ export declare function extractSingleJarFromZip(zipPath: string): Promise<{ jarPath: string; } | { error: string; }>; /** A candidate after scanning: the jar to consider plus its plugin.yml identity. */ export interface ScannedCandidate { candidatePath: string; jarPath: string; modifiedAtMs: number; fromZip: boolean; scanned: ScannedPlugin; } /** * Match scanned candidates against pending expectations (pure — the testable * core of the workflow): * - identity: plugin.yml name, case-insensitive (assigner normalization) * - version: candidate must be equal-or-newer than the expected version * (stores sometimes ship a newer build than the one the check saw) * - ambiguity: multiple candidates for one expectation → newest mtime wins, * the others are reported, never silently dropped */ export declare function matchCandidatesToExpectations(candidates: ScannedCandidate[], expectations: PendingManualExpectation[]): { matches: ManualDropMatch[]; unmatched: UnmatchedCandidate[]; }; /** * Install matched manual drops through the SAME validated path as automatic * downloads: copy to a temp file inside the target dir, run the integrity * gate ({@link validateDownloadedJar}) and the Phase 4 compatibility gates * ({@link runPreInstallGates}), then the shared apply + bookkeeping helpers * from download.ts (snapshot-before-swap, running-server staging, * backup/rename/old-version-cleanup/disabled-state preservation, * plugins-config update, cache refresh, provenance record with the plugin's * assigned sourceType). * * Used directly by the TUI dialog for per-row accepts; `completeManualUpdates` * wraps it with the locks + journal for batch runs. */ export declare function installManualDropMatches(matches: ManualDropMatch[], options: InstallManualDropOptions): Promise<{ installed: InstalledManualUpdate[]; failed: FailedManualUpdate[]; }>; /** * The complete-manual workflow: load pending expectations (offline, from the * update-results cache), discover candidate files (explicit path or the * downloads dir), match, and install through the validated download path. * * Dry-run note: matches referencing a zip-extracted jar point into an OS temp * directory that outlives the call so a follow-up install (e.g. the TUI * dialog's accept) can use them; the OS owns eventual temp cleanup. */ export declare function completeManualUpdates(options: CompleteManualOptions): Promise; //# sourceMappingURL=complete-manual.d.ts.map