feat:移除了弹窗,服务器添加sls

This commit is contained in:
AxTps committed 2026-09-08 22:39:45 +08:00
commit 6a295f9a7a
4082 files changed
+1322534

No files matched your search

@@ -0,0 +1,131 @@
import { forgePromotionsSlimUrl, SERVER_TYPES, type ServerTypeId } from '@modrinth/server'
import {
fetchJson,
resolveServerLauncher,
} from '@/components/multiplayer/servers/server-flow-utils'
import { refresh } from '@/composables/useServers'
import { serverEventListener, servers } from '@/helpers/servers'
import type { DownloadManager } from '@/providers/download-manager'
import { createServerDownloadBridge, type ServerDownloadBridge } from './server-download-bridge'
export interface ServerInstallInputs {
gameVersion: string
loaderVersion?: string
javaPath?: string
memoryMb?: number
}
/**
* A server-type install strategy encapsulates the "how" of materializing a
* server's launchable files. The shared orchestrator (`runServerInstall`) owns
* the cross-cutting concerns — the sidebar download job, progress/log event
* forwarding, and cancellation — so every strategy only drives the backend for
* its specific loader. This is what lets vanilla, Fabric, Paper, Forge and
* (via `ModpackServerInstallStrategy`) modpack servers share one flow.
*/
export interface ServerInstallStrategy {
readonly id: ServerTypeId | 'modpack'
install(serverId: string, inputs: ServerInstallInputs): Promise<void>
}
/** `direct` install-mode types: a single launcher jar is downloaded and booted. */
export class JarServerInstallStrategy implements ServerInstallStrategy {
readonly id: ServerTypeId
constructor(private readonly type: ServerTypeId) {
this.id = type
}
async install(serverId: string, inputs: ServerInstallInputs): Promise<void> {
const jar = await resolveServerLauncher(this.type, inputs.gameVersion, inputs.loaderVersion)
if (!jar) {
throw new Error(`No server launcher available for ${this.type} on ${inputs.gameVersion}`)
}
await servers.downloadFile(serverId, jar.url, jar.filename, jar.sha1)
}
}
/** Resolves the recommended (or, failing that, latest) Forge build for a game version. */
export async function resolveForgeBuild(gameVersion: string): Promise<string> {
const promos = await fetchJson<{ promos: Record<string, string> }>(forgePromotionsSlimUrl())
const build =
promos.promos[`${gameVersion}-recommended`] ?? promos.promos[`${gameVersion}-latest`]
if (!build) throw new Error(`No Forge build available for Minecraft ${gameVersion}`)
return build
}
/** `installer` install-mode Forge: download the installer and run it headlessly. */
export class ForgeServerInstallStrategy implements ServerInstallStrategy {
readonly id = 'forge' as const
async install(serverId: string, inputs: ServerInstallInputs): Promise<void> {
const build = await resolveForgeBuild(inputs.gameVersion)
await servers.installForge(serverId, inputs.gameVersion, build, inputs.javaPath)
}
}
/** Picks the install strategy for a server type from its declared install mode. */
export function getServerInstallStrategy(type: ServerTypeId): ServerInstallStrategy {
const def = SERVER_TYPES[type]
if (def.installMode === 'installer') {
if (type === 'forge') return new ForgeServerInstallStrategy()
throw new Error(`Server type '${type}' installer is not supported yet`)
}
return new JarServerInstallStrategy(type)
}
export interface RunServerInstallOptions {
serverId: string
name: string
inputs: ServerInstallInputs
strategy: ServerInstallStrategy
downloadManager: DownloadManager | null
onProgress?: (progress: { downloaded: number; total: number | null }) => void
onLog?: (line: string) => void
}
/**
* Runs a server install end to end: registers a sidebar download job, forwards
* backend progress/log events to the caller, and invokes the strategy. It does
* not write `eula.txt` or perform cleanup — those depend on flow state and stay
* with the caller.
*/
export async function runServerInstall(options: RunServerInstallOptions): Promise<void> {
const { serverId, name, inputs, strategy, downloadManager, onProgress, onLog } = options
const bridge: ServerDownloadBridge | null = downloadManager
? createServerDownloadBridge(downloadManager, `server-${serverId}`, {
title: name,
icon: null,
provider: 'minecraft',
})
: null
bridge?.cancel(async () => {
await servers.stop(serverId).catch(() => {})
})
const unlisten = await serverEventListener((eventServerId, payload) => {
if (eventServerId !== serverId) return
if (payload.event === 'download_progress') {
const progress = { downloaded: payload.downloaded, total: payload.total ?? null }
onProgress?.(progress)
bridge?.update(progress, null, null)
} else if (payload.event === 'log') {
onLog?.(payload.line)
}
})
try {
await strategy.install(serverId, inputs)
bridge?.complete(true)
// The server was created up front, but the shared list only refreshes on
// demand. When the modal was closed mid-install the `@created` event
// never fires, so refresh here (context-free) to render the new server
// as soon as the install finishes — regardless of modal state.
await refresh().catch(() => {})
} catch (error) {
bridge?.complete(false)
throw error
} finally {
unlisten()
}
}