UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

378 lines (377 loc) 17.5 kB
import Player from "../minecraft/Player"; import NodeStorage from "./NodeStorage"; import LocalEnvironment from "./LocalEnvironment"; import DedicatedServer from "./DedicatedServer"; import { IStatData, IDebugSessionInfo, IProfilerCaptureEvent } from "../debugger/IMinecraftDebugProtocol"; import CreatorTools from "../app/CreatorTools"; import HttpServer from "./HttpServer"; import { IMinecraftStartMessage } from "../app/IMinecraftStartMessage"; import { FileListings } from "./NodeFolder"; import IFolder from "../storage/IFolder"; import { IPackageReference, IWorldSettings } from "../minecraft/IWorldSettings"; import Package from "../app/Package"; import ServerMessage from "./ServerMessage"; import WorldBackupManager from "./WorldBackupManager"; import { IBackupOptions, IBackupResult } from "./IWorldBackupData"; import ManagedWorld from "./ManagedWorld"; import WorldBackup from "./WorldBackup"; export declare const ServerVersionVariants = 5; export declare enum ServerManagerFeatures { all = 0, allWebServices = 1, basicWebServices = 2, dedicatedServerOnly = 3 } export declare enum ServerType { bedrockWindows = 0, bedrockLinux = 1, bedrockWindowsPreview = 2, bedrockLinuxPreview = 3, java = 4 } export interface IServerVersion { serverType?: ServerType; version?: string; versionIndex?: number; downloadedPath?: string; downloadedIndex?: number; downloadPrefix?: string; } /** * Tracks the provisioning state of a slot folder. * Used to avoid unnecessary reprovisioning on stop/start cycles. * * This is persisted to disk as `slot_context.json` in each slot folder * (the "sentinel file") so that context survives server restarts. */ export interface ISlotProvisioningInfo { /** The source server path used to provision this slot */ sourceServerPath: string; /** When the slot was last provisioned (ISO string when serialized) */ provisionedAt: Date | string; /** The server version string (e.g., "1.21.50.24") */ version?: string; /** UUIDs of deployed behavior packs */ deployedBehaviorPackIds?: string[]; /** UUIDs of deployed resource packs */ deployedResourcePackIds?: string[]; /** Whether beta APIs experiment is enabled */ betaApisEnabled?: boolean; /** * When true, the world is transient - not backed up and reset on each deployment. * Useful for development scenarios where you always want a fresh world. */ transientWorld?: boolean; } /** * Options for slot provisioning. */ export interface ISlotProvisioningOptions { /** Force reprovisioning even if source hasn't changed */ forceReprovision?: boolean; /** * When true, the world is transient - not backed up and cleared on each deployment. * Useful for development scenarios where you always want a fresh world. */ transientWorld?: boolean; } export default class ServerManager { #private; dataStorage: NodeStorage; runOnce: boolean | undefined; maxServerIndex: number; primaryServerPort: number; backupWorldFileListings: FileListings; /** * Get the world backup manager. */ get worldBackupManager(): WorldBackupManager | undefined; serverVersions: IServerVersion[]; get isAnyServerRunning(): boolean; get creatorTools(): CreatorTools; get effectiveAutoSourceServerPath(): string; get activeDirectServer(): DedicatedServer; get primaryActiveServer(): DedicatedServer; /** * Get all active servers as an array. */ get activeServers(): DedicatedServer[]; get features(): ServerManagerFeatures; set features(featuresIn: ServerManagerFeatures); get onServerOutput(): import("ste-events").IEvent<DedicatedServer, ServerMessage>; get onServerError(): import("ste-events").IEvent<DedicatedServer, string>; get onServerStarted(): import("ste-events").IEvent<DedicatedServer, string>; get onServerRefreshed(): import("ste-events").IEvent<DedicatedServer, string>; get onServerStarting(): import("ste-events").IEvent<DedicatedServer, string>; get onServerStopped(): import("ste-events").IEvent<DedicatedServer, string>; get onServerStopping(): import("ste-events").IEvent<DedicatedServer, string>; get onTestStarted(): import("ste-events").IEvent<DedicatedServer, string>; get onShutdown(): import("ste-events").IEvent<ServerManager, string>; get onTestFailed(): import("ste-events").IEvent<DedicatedServer, string>; get onTestSucceeded(): import("ste-events").IEvent<DedicatedServer, string>; get onDebugConnected(): import("ste-events").IEvent<DedicatedServer, IDebugSessionInfo>; get onDebugDisconnected(): import("ste-events").IEvent<DedicatedServer, string>; get onDebugStats(): import("ste-events").IEvent<DedicatedServer, { tick: number; stats: IStatData[]; }>; get onDebugPaused(): import("ste-events").IEvent<DedicatedServer, string>; get onDebugResumed(): import("ste-events").IEvent<DedicatedServer, void>; get onProfilerCapture(): import("ste-events").IEvent<DedicatedServer, IProfilerCaptureEvent>; get onPlayerConnected(): import("ste-events").IEvent<DedicatedServer, Player>; get onPlayerDisconnected(): import("ste-events").IEvent<DedicatedServer, Player>; get usePreview(): boolean | undefined; set usePreview(newUsePreview: boolean | undefined); /** * Get the slot prefix used for folder naming. * This allows different contexts (MCP, serve, VS Code) to have isolated server slots. */ get slotPrefix(): string; /** * Set the slot prefix for folder naming. * Different contexts should use different prefixes: * - "mcp" for MCP command → "mcp0", "mcp1", etc. * - "serve" for serve command → "serve0", "serve1", etc. * - "vscode" for VS Code extension → "vscode0", "vscode1", etc. * - "" (empty) for default/backward compatibility → "slot0", "slot1", etc. */ set slotPrefix(prefix: string); /** * Gets the folder name for a given slot number, including the context prefix. * Examples: * - slotPrefix="" → "slot0", "slot1", etc. * - slotPrefix="mcp" → "mcp0", "mcp1", etc. * - slotPrefix="serve" → "serve0", "serve1", etc. * - slotPrefix="vscode" → "vscode0", "vscode1", etc. */ getSlotFolderName(slotNumber: number): string; constructor(env: LocalEnvironment, creatorTools: CreatorTools); /** * Register signal handlers to gracefully shutdown all servers when the process is terminated. * This prevents orphaned bedrock_server processes that could hold ports. */ private registerProcessSignalHandlers; stopWebServer(reason?: string): Promise<void>; shutdown(message: string): Promise<void>; ensureHttpServer(port?: number): HttpServer; get environment(): LocalEnvironment; getRootPath(): string; ensureDedicatedServerForPath(name: string, serverPath: string): DedicatedServer; stopAllDedicatedServers(): Promise<boolean>; private bubblePlayerConnected; private bubblePlayerDisconnected; /** * Handle game events from the dedicated server (e.g., PlayerTravelled, BlockBroken, etc.) * and relay them to WebSocket clients. */ private bubbleServerGameEvent; private bubbleServerError; private bubbleServerOutput; private bubbleServerStarted; /** * Start watching the server's storage folders (world, behavior_packs, resource_packs) * for file changes and broadcast notifications to WebSocket clients. */ private startWatchingServerStorage; /** * Stop watching the server's storage folders when the server stops. */ private stopWatchingServerStorage; private bubbleServerRefreshed; private bubbleServerStarting; private bubbleServerStopping; private bubbleServerStopped; /** * Get the slot number for a given DedicatedServer. * Returns 0 if the server is not found in active servers. */ private getSlotForServer; /** * Push a status update notification via WebSocket. * This replaces the need for clients to poll /api/{slot}/status/ */ private pushStatusNotification; private bubbleTestFailed; private bubbleTestStarted; private bubbleTestSucceeded; private bubbleDebugConnected; private bubbleDebugDisconnected; private bubbleDebugStats; private bubbleDebugPaused; private bubbleDebugResumed; private bubbleProfilerCapture; get effectiveIsUsingPreview(): boolean; getLatestVersionInfo(force: boolean): Promise<boolean>; static getServerTypeStr(serverType: ServerType): string; static getServerTypeFromString(serverVersion: string): ServerType | undefined; deployPackCache(): Promise<void>; downloadLatestSourceServer(): Promise<boolean>; replaceVersion(versionString: string, stub: string): string; private static _getSafeVersion; private tryDownloadDedicatedServer; private ensurePackCacheFolder; private ensureSourceServerFolder; prepare(force?: boolean): Promise<void>; _loadLatestDownloadedSource(): void; ensureDirectServer(directServerPath: string): DedicatedServer; getBasePortForSlot(slotNumber?: number): number; getActiveServer(basePortOrSlot?: number): DedicatedServer; /** * Get all active slot numbers that have running DedicatedServer instances. * Returns an array of slot numbers (0-79). */ getActiveSlots(): number[]; getHashFromStartInfo(startInfo?: IMinecraftStartMessage): string; ensureActiveServer(basePortOrSlot?: number, startInfo?: IMinecraftStartMessage): Promise<DedicatedServer>; /** * Checks if a slot needs reprovisioning based on source server path changes. * Returns true if reprovisioning is needed, false if the slot can be reused as-is. */ needsReprovisioning(slotNumber: number, sourcePath: string): boolean; /** * Prepares a slot-based runtime server folder. * * Uses persistent slot-based folder names (e.g., `slot0/`, `slot1/`) instead of * timestamp-based names. This keeps the `bedrock_server.exe` path constant, * preventing Windows Firewall from prompting on every server start. * * **Smart Reprovisioning**: If the slot already exists and was provisioned from * the same source server path, this method returns quickly without re-doing * file operations. This makes stop/start cycles fast. * * **Backup Before Clean**: If the slot exists but needs reprovisioning (different * source version), world data is backed up before any destructive operations. * For transient worlds, the world folder is cleared instead of backed up. * * @param sourcePath - Path to the downloaded source server (e.g., bwv1.21.50.24/) * @param slotNumber - The slot number (0-79) for this server instance * @param options - Provisioning options (forceReprovision, transientWorld) * @returns Object with name and path for the runtime server */ prepareSlotServerPath(sourcePath: string, slotNumber?: number, options?: ISlotProvisioningOptions): Promise<{ name: string; path: string; wasReprovisioned: boolean; }>; /** * Saves the slot context/sentinel file to disk. * This file records why/how the slot was created so we can detect context changes. */ saveSlotContext(slotNumber: number, slotServerPath: string, info: ISlotProvisioningInfo): void; /** * Loads the slot context/sentinel file from disk. * Returns undefined if the file doesn't exist or is invalid. */ loadSlotContext(slotNumber: number, slotServerPath: string): ISlotProvisioningInfo | undefined; /** * Extracts the version string from a source server path. * E.g., "bwv1.21.50.24/" -> "1.21.50.24" */ extractVersionFromSourcePath(sourcePath: string): string | undefined; /** * Backs up world data from a slot folder before destructive operations. * Uses the centralized WorldBackupManager for backup storage and deduplication. * Skips backup if the slot is marked as transient (transientWorld flag). */ backupSlotWorldData(slotNumber: number, slotServerPath: string): Promise<void>; /** * Clears world data from a slot folder for transient worlds. * This resets the world to a fresh state, only keeping level.dat and level_name.txt * to preserve world name and seed settings. */ clearSlotWorldData(slotNumber: number, slotServerPath: string): Promise<void>; /** * @deprecated Use prepareSlotServerPath instead. This method is kept for backwards * compatibility but creates timestamp-based folders that cause Windows Firewall prompts. */ prepareTempServerNameAndPath(sourcePath: string): Promise<{ name: string; path: string; wasReprovisioned: boolean; }>; preparePacksAndTemplates(targetPath: string, targetWorldFolder: IFolder, worldSettings: IWorldSettings): Promise<void>; addPackageFolders(targetPath: string, pack: Package, packRef: IPackageReference): Promise<void>; addWorldTemplate(targetWorldFolder: IFolder, pack: Package): Promise<void>; addPackFolderReferences(targetPath: string, folder: IFolder, folderModifier: string, packRefSet: IPackageReference): Promise<void>; copyWorldFiles(targetWorldFolder: IFolder, sourceFolder: IFolder): Promise<void>; addChildFolderReferences(targetPath: string, loadedFolder: IFolder, folderModifier: string, targetSubFolder: string): void; cleanDedicatedServerSymLinkFolder(folder: string, tempServerPath: string): void; createSymLinkFolder(targetPath: string, sourcePath: string): void; createDedicatedServerSymLinkFolder(sourcePath: string, folder: string, tempServerPath: string): void; cleanDedicatedServerSymLinkFile(file: string, tempServerPath: string): void; createDedicatedServerFile(sourcePath: string, file: string, tempServerPath: string): void; /** * Updates a symlink folder for slot-based server provisioning. * If the target exists (symlink or directory), it is removed and recreated. * This allows updating an existing slot to point to a new source server version. */ updateDedicatedServerSymLinkFolder(sourcePath: string, folder: string, slotServerPath: string): void; /** * Updates a file for slot-based server provisioning. * Overwrites the existing file in place, keeping the same path. * This is critical for bedrock_server.exe to avoid Windows Firewall prompts. */ updateDedicatedServerFile(sourcePath: string, file: string, slotServerPath: string): void; /** * List all managed worlds with their backup information. */ listManagedWorlds(): Promise<ManagedWorld[]>; /** * Get a managed world by its ID. */ getManagedWorld(worldId: string): Promise<ManagedWorld | undefined>; /** * Create a new managed world with a unique ID. * @param friendlyName Human-readable name for the world * @param description Optional description * @returns The newly created ManagedWorld */ createManagedWorld(friendlyName: string, description?: string): Promise<ManagedWorld>; /** * Create a backup of a world from a source folder. * @param worldId The ID of the managed world to backup to * @param sourceFolder The folder containing the world data to backup * @param options Backup options * @returns The backup result */ createWorldBackup(worldId: string, sourceFolder: IFolder, options?: IBackupOptions): Promise<IBackupResult>; /** * Restore a backup to a target folder. * @param worldId The ID of the managed world * @param timestamp The backup timestamp to restore (or undefined for latest) * @param targetFolder The folder to restore to */ restoreWorldBackup(worldId: string, timestamp: number | undefined, targetFolder: IFolder): Promise<void>; /** * Export a backup as a .mcworld file. * @param worldId The ID of the managed world * @param timestamp The backup timestamp to export (or undefined for latest) * @param outputPath The path to write the .mcworld file to */ exportBackupAsMcWorld(worldId: string, timestamp: number | undefined, outputPath: string): Promise<void>; /** * Get all backups for a managed world. * @param worldId The ID of the managed world */ getWorldBackups(worldId: string): Promise<WorldBackup[]>; /** * Delete a specific backup. * @param worldId The ID of the managed world * @param timestamp The backup timestamp to delete */ deleteWorldBackup(worldId: string, timestamp: number): Promise<void>; /** * Prune old backups for a world, keeping only the most recent ones. * @param worldId The ID of the managed world * @param keepCount Number of backups to keep (default 10) */ pruneWorldBackups(worldId: string, keepCount?: number): Promise<number>; /** * Get or create a managed world for a given slot. * This creates a mapping between server slots and managed worlds. * @param slotNumber The server slot number * @param createIfMissing If true, creates a new world if none exists for the slot */ getOrCreateWorldForSlot(slotNumber: number, createIfMissing?: boolean): Promise<ManagedWorld | undefined>; register(): void; }