@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
378 lines (377 loc) • 17.5 kB
TypeScript
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;
}