@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
467 lines (466 loc) • 22 kB
JavaScript
;
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HostType = exports.CreatorToolsThemeStyle = void 0;
/*
* ==========================================================================================
* CREATORTOOLS HOST - PLATFORM ABSTRACTION LAYER NOTES
* ==========================================================================================
*
* OVERVIEW:
* ---------
* CreatorToolsHost is the central platform abstraction layer that allows MCT to run across
* multiple environments: web browser, NodeJS CLI, Electron app, and VS Code extension.
* It provides static methods and properties for platform detection, storage initialization,
* and cross-platform thunks for functionality that differs by platform.
*
* HOST TYPES (HostType enum):
* ---------------------------
* - web (0): Pure browser, no backend services (uses BrowserStorage/localStorage)
* - toolsNodejs (1): NodeJS command line tool (uses NodeStorage/fs)
* - electronNodeJs (2): Electron main process (NodeJS side)
* - electronWeb (3): Electron renderer process (browser side, uses ElectronStorage via IPC)
* - vsCodeMainNodeJs (4): VS Code extension host (NodeJS side)
* - vsCodeMainWeb (5): VS Code webview connected to extension host
* - vsCodeWebService (6): VS Code web extension service worker
* - vsCodeWebWeb (7): VS Code web extension webview
* - webPlusServices (8): Browser with AppServiceProxy backend
* - testLocal (9): Local testing environment
*
* STORAGE INITIALIZATION:
* -----------------------
* The init() method sets up storage based on hostType:
* - prefsStorage: User preferences (BrowserStorage in web, ElectronStorage in Electron)
* - projectsStorage: Project files and metadata
* - deploymentStorage[]: Array of deployment targets indexed by DeploymentTargetType
* - worldStorage: Minecraft worlds
* - packStorage: Downloaded/cached packs
* - workingStorage: Temporary working files
*
* CROSS-PLATFORM THUNKS:
* ----------------------
* These function properties are set based on platform capabilities:
* - generateCryptoRandomNumber: Secure random number generation
* - localFolderExists / localFileExists: File system checks (only in NodeJS contexts)
* - ensureLocalFolder: Create local folder (only in NodeJS contexts)
* - createMinecraft / canCreateMinecraft: Minecraft instance creation (Electron/VSCode)
*
* CONTENT ROOTS:
* --------------
* - contentWebRoot: Base URL for APP data (forms, catalogs, snippets, typedefs, mccat.json).
* Set to "/" for local HTTP server, or full URL for web hosting.
* Used for: data/forms/, data/mccat.json, data/typedefs.*.json, data/mci/, data/mch/
*
* - vanillaContentRoot: Base URL for VANILLA MINECRAFT assets (textures, models, resource packs).
* These are large files (~500MB+) not bundled with CLI tool, hosted at mctools.dev.
* Used for: res/latest/van/release/, res/latest/van/preview/, res/latest/van/serve/
*
* - getVanillaContentRoot(): Returns vanillaContentRoot if set, else contentWebRoot.
* Use this for all vanilla resource loading (textures, blocks.json, terrain_texture.json).
*
* INITIALIZATION FLOW:
* --------------------
* 1. Global variables (g_contentRoot, g_isVsCodeMain, etc.) are injected by build system
* 2. init() reads globals and sets hostType
* 3. AppServiceProxy.init() establishes IPC if in Electron/VSCode
* 4. Database.loadVanillaCatalog() loads mccat.json
* 5. Storage objects are created based on detected platform
* 6. CreatorTools singleton is instantiated with storage references
*
* RELATED FILES:
* --------------
* - CreatorTools.ts: Main application object, instantiated by CreatorToolsHost
* - AppServiceProxy.ts: IPC communication layer for Electron/VSCode
* - BrowserStorage.ts, ElectronStorage.ts, NodeStorage.ts: Storage implementations
* - Database.ts: Loads vanilla catalog and form definitions
*
* COMMON PATTERNS:
* ----------------
* - Check platform: CreatorToolsHost.isNodeJs, .isWeb, .isVsCode, .isAppServiceWeb
* - Get main app: CreatorToolsHost.getCreatorTools()
* - Listen for init: CreatorToolsHost.onInitialized.subscribe(handler)
* - Theme changes: CreatorToolsHost.onThemeChanged.subscribe(handler)
*
* ==========================================================================================
*/
const CreatorTools_1 = __importDefault(require("./CreatorTools"));
const Database_1 = __importDefault(require("../minecraft/Database"));
const BrowserStorage_1 = __importDefault(require("../storage/BrowserStorage"));
const ElectronStorage_1 = __importDefault(require("../electronclient/ElectronStorage"));
const ste_events_1 = require("ste-events");
const AppServiceProxy_1 = __importDefault(require("../core/AppServiceProxy"));
const StorageUtilities_1 = __importDefault(require("../storage/StorageUtilities"));
const CreatorToolsCommands_1 = __importDefault(require("./CreatorToolsCommands"));
const DeploymentTarget_1 = require("./DeploymentTarget");
var CreatorToolsThemeStyle;
(function (CreatorToolsThemeStyle) {
CreatorToolsThemeStyle[CreatorToolsThemeStyle["dark"] = 0] = "dark";
CreatorToolsThemeStyle[CreatorToolsThemeStyle["light"] = 1] = "light";
CreatorToolsThemeStyle[CreatorToolsThemeStyle["highContrastDark"] = 2] = "highContrastDark";
CreatorToolsThemeStyle[CreatorToolsThemeStyle["highContrastLight"] = 3] = "highContrastLight";
})(CreatorToolsThemeStyle || (exports.CreatorToolsThemeStyle = CreatorToolsThemeStyle = {}));
var HostType;
(function (HostType) {
HostType[HostType["web"] = 0] = "web";
HostType[HostType["toolsNodejs"] = 1] = "toolsNodejs";
HostType[HostType["electronNodeJs"] = 2] = "electronNodeJs";
HostType[HostType["electronWeb"] = 3] = "electronWeb";
HostType[HostType["vsCodeMainNodeJs"] = 4] = "vsCodeMainNodeJs";
HostType[HostType["vsCodeMainWeb"] = 5] = "vsCodeMainWeb";
HostType[HostType["vsCodeWebService"] = 6] = "vsCodeWebService";
HostType[HostType["vsCodeWebWeb"] = 7] = "vsCodeWebWeb";
HostType[HostType["webPlusServices"] = 8] = "webPlusServices";
HostType[HostType["testLocal"] = 9] = "testLocal";
})(HostType || (exports.HostType = HostType = {}));
/// CreatorToolsHost mostly serves as a static app loader for the really-core Carto app object.
class CreatorToolsHost {
static _creatorTools;
static _onInitialized = new ste_events_1.EventDispatcher();
static _onThemeChanged = new ste_events_1.EventDispatcher();
static _initializing = false;
static _initialized = false;
static isLocalNode = false;
static fullLocalStorage = false;
static hostType = HostType.web;
static hostManager = undefined;
static _theme = CreatorToolsThemeStyle.dark;
/**
* True when the OS/browser is in a forced-colors (high contrast) mode.
* This is orthogonal to dark/light — HC Dark sets theme=dark + isHighContrast,
* HC Light sets theme=light + isHighContrast.
*/
static isHighContrast = false;
static retrieveDataFromWebContentRoot = false;
static contentWebRoot = "";
static vanillaContentRoot = ""; // URL root for vanilla textures, samples, definitions (e.g., https://mctools.dev/)
static projectPath = "";
static focusPath = undefined;
static baseUrl = "";
/**
* Gets the effective root URL for vanilla content (textures, samples, definitions).
* Returns vanillaContentRoot if set, otherwise falls back to contentRoot.
*/
static getVanillaContentRoot() {
return this.vanillaContentRoot || this.contentWebRoot || "";
}
/**
* Gets the current theme style
*/
static get theme() {
return this._theme;
}
/**
* Sets the theme style and notifies listeners
*/
static set theme(value) {
if (this._theme !== value) {
this._theme = value;
this._onThemeChanged.dispatch(CreatorToolsHost, value);
}
}
/**
* Event fired when theme changes. Use this to update FluentUI components
* that are initialized outside of React's render cycle.
*/
static get onThemeChanged() {
return this._onThemeChanged.asEvent();
}
static initialMode = null;
static modeParameter = null;
static contentUrl = null;
static readOnly = false;
static postMessage;
static prefsStorage = null;
static projectsStorage = null;
static deploymentStorage = [];
static worldStorage = null;
static packStorage = null;
static workingStorage = null;
static generateCryptoRandomNumber;
static generateUuid;
static localFolderExists;
static localFileExists;
static ensureLocalFolder;
static createMinecraft;
static canCreateMinecraft;
// Image codec thunks - set by Node.js environments to provide platform-specific implementations
static decodePng;
static encodeToPng;
static _onMessage = new ste_events_1.EventDispatcher();
static get isNodeJs() {
return (this.hostType === HostType.electronNodeJs ||
this.hostType === HostType.toolsNodejs ||
this.hostType === HostType.vsCodeMainNodeJs);
}
static get isVsCode() {
return (this.hostType === HostType.vsCodeMainNodeJs ||
this.hostType === HostType.vsCodeWebService ||
this.hostType === HostType.vsCodeMainWeb ||
this.hostType === HostType.vsCodeWebWeb);
}
static get isWeb() {
return (this.hostType === HostType.webPlusServices ||
this.hostType === HostType.web ||
this.hostType === HostType.electronWeb ||
this.hostType === HostType.vsCodeMainWeb ||
this.hostType === HostType.vsCodeWebWeb);
}
static get isAppServiceWeb() {
return this.hostType === HostType.electronWeb || this.hostType === HostType.vsCodeMainWeb;
}
static get onMessage() {
return CreatorToolsHost._onMessage.asEvent();
}
static get onInitialized() {
return CreatorToolsHost._onInitialized.asEvent();
}
static getCreatorTools() {
if (!this._initialized) {
this.init();
}
return CreatorToolsHost._creatorTools;
}
static notifyNewMessage(source, message) {
CreatorToolsHost._onMessage.dispatch(source, message);
}
static setHostType(type) {
this.hostType = type;
}
static init() {
if (CreatorToolsHost._initializing || CreatorToolsHost._initialized) {
return;
}
CreatorToolsHost._initializing = true;
//@ts-ignore
if (typeof g_contentRoot !== "undefined") {
//@ts-ignore
CreatorToolsHost.contentWebRoot = StorageUtilities_1.default.ensureEndsWithDelimiter(g_contentRoot);
}
//@ts-ignore
if (typeof g_vanillaContentRoot !== "undefined") {
//@ts-ignore
CreatorToolsHost.vanillaContentRoot = StorageUtilities_1.default.ensureEndsWithDelimiter(g_vanillaContentRoot);
}
//@ts-ignore
if (typeof g_initialMode !== "undefined") {
//@ts-ignore
CreatorToolsHost.initialMode = g_initialMode;
}
//@ts-ignore
if (typeof g_modeParameter !== "undefined") {
//@ts-ignore
CreatorToolsHost.modeParameter = g_modeParameter;
}
//@ts-ignore
if (typeof g_isVsCodeMain !== "undefined") {
//@ts-ignore
if (g_isVsCodeMain) {
CreatorToolsHost.hostType = HostType.vsCodeMainWeb;
}
}
//@ts-ignore
if (typeof g_isVsCodeWeb !== "undefined") {
//@ts-ignore
if (g_isVsCodeWeb) {
CreatorToolsHost.hostType = HostType.vsCodeWebWeb;
}
}
//@ts-ignore
if (typeof g_projectPath !== "undefined") {
//@ts-ignore
CreatorToolsHost.projectPath = g_projectPath;
}
//@ts-ignore
if (typeof g_baseUrl !== "undefined") {
//@ts-ignore
CreatorToolsHost.baseUrl = g_baseUrl;
}
//@ts-ignore
if (typeof g_contentUrl !== "undefined") {
//@ts-ignore
CreatorToolsHost.contentUrl = g_contentUrl;
}
//@ts-ignore
if (typeof g_readOnly !== "undefined") {
//@ts-ignore
CreatorToolsHost.readOnly = g_readOnly === true;
}
AppServiceProxy_1.default.init();
Database_1.default.loadVanillaCatalog();
CreatorToolsCommands_1.default.registerCommonCommands();
CreatorToolsHost.generateCryptoRandomNumber = (toVal) => {
const ct = CreatorToolsHost.getCreatorTools();
if (!ct || !ct.local) {
throw new Error("Could not generate key.");
}
return ct.local.generateCryptoRandomNumber(toVal);
};
CreatorToolsHost.generateUuid = () => {
const ct = CreatorToolsHost.getCreatorTools();
if (!ct || !ct.local) {
throw new Error("Could not generate UUID.");
}
return ct.local.generateUuid();
};
// Set up image codec thunks for Node.js environments
CreatorToolsHost.decodePng = (data) => {
const ct = CreatorToolsHost.getCreatorTools();
if (!ct || !ct.local) {
return undefined;
}
return ct.local.decodePng(data);
};
CreatorToolsHost.encodeToPng = (pixels, width, height) => {
const ct = CreatorToolsHost.getCreatorTools();
if (!ct || !ct.local) {
return undefined;
}
return ct.local.encodeToPng(pixels, width, height);
};
// @ts-ignore
if (typeof window !== "undefined" && window.crypto) {
CreatorToolsHost.generateCryptoRandomNumber = (toVal) => {
// Use rejection sampling to avoid modulo bias when generating random numbers
// from a cryptographically secure source
const maxUint32 = 0xffffffff;
const limit = maxUint32 - (maxUint32 % toVal);
let randomValue;
do {
// @ts-ignore
randomValue = window.crypto.getRandomValues(new Uint32Array(1))[0];
} while (randomValue >= limit);
return randomValue % toVal;
};
// @ts-ignore
if (window.crypto.randomUUID) {
CreatorToolsHost.generateUuid = () => {
// @ts-ignore
return window.crypto.randomUUID();
};
}
else {
// Fallback for older browsers using crypto.getRandomValues
CreatorToolsHost.generateUuid = () => {
// @ts-ignore
const bytes = window.crypto.getRandomValues(new Uint8Array(16));
// Set version 4 (random) UUID bits
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
};
}
}
if (CreatorToolsHost.projectsStorage !== null && CreatorToolsHost.prefsStorage !== null) {
}
else if (AppServiceProxy_1.default.hasAppService) {
let ls = new ElectronStorage_1.default("<DOCP>", "prefs");
ls.rootFolder.ensureExists();
CreatorToolsHost.prefsStorage = ls;
ls = new ElectronStorage_1.default("<DOCP>", "working");
ls.rootFolder.ensureExists();
CreatorToolsHost.workingStorage = ls;
ls = new ElectronStorage_1.default("<DOCP>", "projects");
ls.rootFolder.ensureExists();
CreatorToolsHost.projectsStorage = ls;
ls = new ElectronStorage_1.default("<DOCP>", "worlds");
ls.rootFolder.ensureExists();
CreatorToolsHost.worldStorage = ls;
ls = new ElectronStorage_1.default("<DOCP>", "packs");
ls.rootFolder.ensureExists();
CreatorToolsHost.packStorage = ls;
// @ts-ignore
if (typeof window !== "undefined") {
// @ts-ignore
let basePath = window.location.href;
const lastSlash = basePath.lastIndexOf("/");
if (lastSlash >= 0) {
CreatorToolsHost.contentWebRoot = basePath.substring(0, lastSlash + 1);
}
}
let minecraftPath = "<BDRK>";
ls = new ElectronStorage_1.default(minecraftPath, "");
ls.getAvailable();
CreatorToolsHost.deploymentStorage[DeploymentTarget_1.DeploymentTargetType.bedrock] = ls;
let minecraftPreviewPath = "<BDPV>";
ls = new ElectronStorage_1.default(minecraftPreviewPath, "");
ls.getAvailable();
CreatorToolsHost.deploymentStorage[DeploymentTarget_1.DeploymentTargetType.bedrockPreview] = ls;
//@ts-ignore
}
else if (typeof window !== "undefined") {
// Guard: If we're in Electron context but AppServiceProxy isn't ready, that's an initialization error.
// Electron renderer should NEVER use BrowserStorage - it should use ElectronStorage which proxies to NodeJS.
// @ts-ignore
if (typeof window.api !== "undefined") {
throw new Error("BrowserStorage initialization attempted in Electron context. " +
"Electron renderer must use ElectronStorage. " +
"Ensure AppServiceProxy.hasAppService is true before CreatorToolsHost.init() is called.");
}
CreatorToolsHost.prefsStorage = new BrowserStorage_1.default("mctprefs");
CreatorToolsHost.projectsStorage = new BrowserStorage_1.default("mctprojects");
CreatorToolsHost.deploymentStorage[DeploymentTarget_1.DeploymentTargetType.bedrock] = new BrowserStorage_1.default("mctdeploy");
CreatorToolsHost.workingStorage = new BrowserStorage_1.default("mctworking");
CreatorToolsHost.worldStorage = new BrowserStorage_1.default("mctworlds");
CreatorToolsHost.packStorage = new BrowserStorage_1.default("mctpacks");
}
if (CreatorToolsHost.prefsStorage === null || CreatorToolsHost.projectsStorage === null) {
throw new Error("Unexpected uninitialized storage.");
}
CreatorToolsHost._creatorTools = new CreatorTools_1.default(CreatorToolsHost.prefsStorage, CreatorToolsHost.projectsStorage, CreatorToolsHost.deploymentStorage, CreatorToolsHost.worldStorage, CreatorToolsHost.packStorage, CreatorToolsHost.workingStorage, CreatorToolsHost.contentWebRoot);
if (CreatorToolsHost.ensureLocalFolder) {
CreatorToolsHost._creatorTools.ensureLocalFolder = CreatorToolsHost.ensureLocalFolder;
CreatorToolsHost._creatorTools.localFolderExists = CreatorToolsHost.localFolderExists;
CreatorToolsHost._creatorTools.localFileExists = CreatorToolsHost.localFileExists;
}
else if (CreatorToolsHost.hostType === HostType.electronWeb) {
CreatorToolsHost._creatorTools.ensureLocalFolder = CreatorToolsHost._ensureElectronLocalFolder;
CreatorToolsHost._creatorTools.localFolderExists = CreatorToolsHost._localFolderExists;
CreatorToolsHost._creatorTools.localFileExists = CreatorToolsHost._localFileExists;
}
CreatorToolsHost._creatorTools.createMinecraft = CreatorToolsHost.createMinecraft;
CreatorToolsHost._creatorTools.canCreateMinecraft = CreatorToolsHost.canCreateMinecraft;
CreatorToolsHost._initialized = true;
this._onInitialized.dispatch(CreatorToolsHost._creatorTools, CreatorToolsHost._creatorTools);
}
static async _localFolderExists(path) {
const ls = new ElectronStorage_1.default(path, "");
let result = ls.available;
if (!result) {
result = await ls.getAvailable();
if (!result) {
return false;
}
}
return await ls.rootFolder.exists();
}
static async _localFileExists(path) {
const folderPath = StorageUtilities_1.default.getFolderPath(path);
const fileName = StorageUtilities_1.default.getLeafName(path);
if (!fileName || fileName.length < 2 || !folderPath || folderPath.length < 2) {
throw new Error("Could not process file with path: `" + path + "`");
}
const ls = new ElectronStorage_1.default(folderPath, "");
const file = ls.rootFolder.ensureFile(fileName);
return await file.exists();
}
static _ensureElectronLocalFolder(path) {
// Reuse existing storage if one already exists for this path
// (e.g., from a prior _localFolderExists call that set up the file watcher).
// Creating a new ElectronStorage overwrites electronStorages[path], which could
// orphan an existing storage that has its file watcher configured.
let ls = ElectronStorage_1.default.electronStorages[path];
if (!ls) {
ls = new ElectronStorage_1.default(path, "");
// Kick off getAvailable() to set up the fs.watch file watcher in the main process.
// Without this, externally created files (e.g., by MCP tools) won't be detected.
ls.getAvailable();
}
return ls.rootFolder;
}
}
exports.default = CreatorToolsHost;