UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

555 lines (553 loc) 28.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExportMode = void 0; const Utilities_1 = require("../core/Utilities"); const StorageUtilities_1 = require("../storage/StorageUtilities"); const DataFormUtilities_1 = require("../dataform/DataFormUtilities"); const IField_1 = require("../dataform/IField"); const EntityTypeDefinition_1 = require("../minecraft/EntityTypeDefinition"); const Database_1 = require("../minecraft/Database"); const Log_1 = require("../core/Log"); var ExportMode; (function (ExportMode) { ExportMode[ExportMode["other"] = 0] = "other"; ExportMode[ExportMode["triggers"] = 1] = "triggers"; ExportMode[ExportMode["blockComponents"] = 2] = "blockComponents"; ExportMode[ExportMode["itemComponents"] = 3] = "itemComponents"; ExportMode[ExportMode["entityComponents"] = 4] = "entityComponents"; ExportMode[ExportMode["AIGoals"] = 5] = "AIGoals"; ExportMode[ExportMode["visuals"] = 6] = "visuals"; ExportMode[ExportMode["fogs"] = 7] = "fogs"; ExportMode[ExportMode["websockets"] = 8] = "websockets"; ExportMode[ExportMode["filters"] = 9] = "filters"; ExportMode[ExportMode["MCToolsVal"] = 10] = "MCToolsVal"; ExportMode[ExportMode["eventResponses"] = 11] = "eventResponses"; ExportMode[ExportMode["clientBiomes"] = 12] = "clientBiomes"; ExportMode[ExportMode["biomes"] = 13] = "biomes"; ExportMode[ExportMode["features"] = 14] = "features"; ExportMode[ExportMode["featureCore"] = 15] = "featureCore"; ExportMode[ExportMode["clientDeferredRendering"] = 16] = "clientDeferredRendering"; })(ExportMode = exports.ExportMode || (exports.ExportMode = {})); class FormDefinitionTypeScriptGenerator { async generateTypes(formJsonInputFolder, outputFolder) { const formsByPath = {}; await this.loadFormJsonFromFolder(formsByPath, formJsonInputFolder, outputFolder); this.exportTypeScriptDocs(formsByPath, outputFolder, ExportMode.features, "/features/", "/features/minecraft_", "Feature"); this.exportTypeScriptDocs(formsByPath, outputFolder, ExportMode.featureCore, "/features/", "/feature/feature", "Feature Rule"); this.exportTypeScriptDocs(formsByPath, outputFolder, ExportMode.AIGoals, "/entity/", "/entity/minecraft_behavior", "Entity"); this.exportTypeScriptDocs(formsByPath, outputFolder, ExportMode.visuals, "/visual/", "/visual/", "Visuals"); this.exportTypeScriptDocs(formsByPath, outputFolder, ExportMode.fogs, "/fogs/", "/fogs/", "Fogs"); this.exportTypeScriptDocs(formsByPath, outputFolder, ExportMode.clientBiomes, "/client_biome/", "/client_biome/", "Client Biomes"); this.exportTypeScriptDocs(formsByPath, outputFolder, ExportMode.clientBiomes, "/deferred_rendering/", "/client_deferred_rendering/", "Client Biomes"); this.exportTypeScriptDocs(formsByPath, outputFolder, ExportMode.websockets, "/websockets/", "/websockets/", "Websockets"); this.exportTypeScriptDocs(formsByPath, outputFolder, ExportMode.triggers, "/entity/entityTriggers/", "/entity/minecraft_on", "Entity"); this.exportTypeScriptDocs(formsByPath, outputFolder, ExportMode.filters, "/entity/filters/", "/entityfilters/", "Entity Filters"); this.exportTypeScriptDocs(formsByPath, outputFolder, ExportMode.eventResponses, "/entity/eventActions/", "/entityevents/", "Entity Actions"); this.exportTypeScriptDocs(formsByPath, outputFolder, ExportMode.blockComponents, "/block/blockComponents/", "/block/minecraft_", "Block Components"); this.exportTypeScriptDocs(formsByPath, outputFolder, ExportMode.itemComponents, "/item/itemComponents/", "/item/minecraft_", "Items"); this.exportTypeScriptDocs(formsByPath, outputFolder, ExportMode.entityComponents, "/entity/entityComponents/", "/entity/minecraft_", "Entity"); this.exportTypeScriptDocs(formsByPath, outputFolder, ExportMode.clientBiomes, "/clientBiomes/components/", "/clientbiome/", "Client Biome"); this.exportTypeScriptDocs(formsByPath, outputFolder, ExportMode.biomes, "/biomes/components/", "/biome/", "Biome"); } getFileNameFromBaseName(baseName, exportMode) { let fileName = baseName; return fileName; } async exportTypeScriptDocs(formsByPath, outputFolder, exportMode, subFolderPath, formsPath, categoryPlural) { const targetFolder = await outputFolder.ensureFolderFromRelativePath(subFolderPath); if (!targetFolder) { return; } let hasEnsuredFolder = false; formsByPath = this.getFormsFromFilter(formsByPath, formsPath, exportMode); for (const formPath in formsByPath) { const formO = formsByPath[formPath]; if (formO) { let baseName = StorageUtilities_1.default.getBaseFromName(StorageUtilities_1.default.getLeafName(formPath)); if (baseName.endsWith(".form")) { baseName = baseName.substring(0, baseName.length - 5); } if (!hasEnsuredFolder) { await targetFolder.ensureExists(); hasEnsuredFolder = true; } let typeName = formO.id ? formO.id : formO.title; if (!typeName) { Log_1.default.unexpectedUndefined("Form: " + baseName); return; } typeName = Utilities_1.default.javascriptifyName(typeName, true); let fileName = this.getFileNameFromBaseName(typeName, exportMode); const markdownFile = targetFolder.ensureFile(fileName + ".d.ts"); await this.saveFormDefinitionTypeScriptDocForm(markdownFile, formO, baseName, exportMode, categoryPlural, Utilities_1.default.countChar(subFolderPath, "/")); } } } async saveFormDefinitionTypeScriptDocForm(dtsFile, form, baseName, exportMode, category, folderDepth) { const content = []; let canonName = "minecraft:" + EntityTypeDefinition_1.default.getComponentFromBaseFileName(baseName); if (exportMode === ExportMode.websockets && form.id) { canonName = form.id; } content.push("// Copyright (c) Microsoft Corporation."); content.push("// Licensed under the MIT License."); content.push("// Type definitions for working with Minecraft Bedrock Edition pack JSON schemas."); content.push("// Project: https://learn.microsoft.com/minecraft/creator/"); content.push(""); content.push("/**"); content.push(" * @packageDocumentation"); content.push(" * Contains types for working with various Minecraft Bedrock Edition JSON schemas."); content.push(" * "); content.push(" * " + category + " Documentation - " + canonName); if (form.samples) { content.push(" * "); content.push(" * " + canonName + " Samples"); let samplesAdded = 0; const linesAdded = []; for (const samplePath in form.samples) { let sampleArr = form.samples[samplePath]; let addedHeader = false; if (sampleArr && samplesAdded < 12) { const sampBaseName = StorageUtilities_1.default.getBaseFromName(StorageUtilities_1.default.getLeafName(samplePath)); let targetPath = samplePath; for (const sample of sampleArr) { let line = ""; if (baseName.startsWith("minecraft_") && (typeof sample.content !== "string" || !sample.content.startsWith("minecraft:"))) { line += '"' + canonName + '": '; } if (typeof sample.content === "object" || Array.isArray(sample.content)) { line += JSON.stringify(sample.content, undefined, 2) + "\r\n"; } else { if (typeof sample.content === "string") { let cont = sample.content.trim(); if (cont.startsWith("{") && cont.endsWith("}")) { line += cont; } else { line += '"' + cont; } } else { line += sample.content; } } if (!linesAdded.includes(line)) { if (!addedHeader) { addedHeader = true; if (targetPath !== "samples" && targetPath !== "sample") { if (targetPath.startsWith("/vanilla")) { targetPath = "https://github.com/Mojang/bedrock-samples/tree/preview" + targetPath.substring(8); } else if (targetPath.startsWith("/samples")) { targetPath = "https://github.com/microsoft/minecraft-samples/tree/main" + targetPath.substring(8); } content.push(""); content.push(Utilities_1.default.humanifyMinecraftName(sampBaseName.substring(0, 1).toUpperCase() + sampBaseName.substring(1)) + " - " + targetPath); content.push(""); } } if (sampleArr.length > 1) { content.push(" * At " + sample.path + ": "); } linesAdded.push(line); content.push(line); samplesAdded++; } } } } } content.push(" */"); content.push("\r\nimport * as jsoncommon from './" + "../".repeat(folderDepth) + "jsoncommon';\r\n"); await this.appendType(form, content, 0); dtsFile.setContent(content.join("\r\n")); await dtsFile.saveContent(); } static sanitizeDescription(description) { description = description.trim(); description = Utilities_1.default.ensureFirstCharIsUpperCase(description); if (description.length > 10 && !description.endsWith(".") && !description.endsWith(":")) { description += "."; } return description; } getFileNameFromJsonKey(key) { key = key.toLowerCase(); key = key.replace(/ /gi, "_"); key = key.replace(/::/gi, "_"); key = key.replace(/:/gi, "_"); return key; } async appendType(form, content, depth, altTitle) { content.push("/**"); let typeName = altTitle ? altTitle : form.id ? form.id : form.title; if (!typeName) { Log_1.default.unexpectedUndefined("Type: " + JSON.stringify(form)); return; } typeName = Utilities_1.default.javascriptifyName(typeName, true); if (form.title) { FormDefinitionTypeScriptGenerator.appendLongTextWithAsterisks(content, form.title + (form.id ? " (" + form.id + ")" : ""), 60, 1); } if (form.description) { FormDefinitionTypeScriptGenerator.appendLongTextWithAsterisks(content, FormDefinitionTypeScriptGenerator.sanitizeDescription(form.description), 60, 1); } if (form.note) { FormDefinitionTypeScriptGenerator.appendLongTextWithAsterisks(content, "Note: " + FormDefinitionTypeScriptGenerator.sanitizeDescription(form.note), 60, 1); } if (form.note2) { FormDefinitionTypeScriptGenerator.appendLongTextWithAsterisks(content, "Note: " + FormDefinitionTypeScriptGenerator.sanitizeDescription(form.note2), 60, 1); } if (form.note3) { FormDefinitionTypeScriptGenerator.appendLongTextWithAsterisks(content, "Note: " + FormDefinitionTypeScriptGenerator.sanitizeDescription(form.note3), 60, 1); } if (form.isDeprecated) { content.push(" * IMPORTANT"); content.push(" * This type is now deprecated, and no longer in use in the latest versions of Minecraft."); content.push(" * "); } if (form.isInternal) { content.push(" * IMPORTANT"); content.push(" * This type is internal to vanilla Minecraft usage, and is not functional or supported within custom Minecraft content."); content.push(" * "); } const scalarField = DataFormUtilities_1.default.getScalarField(form); if (scalarField) { content.push(" * NOTE: Alternate Simple Representations\r\n"); for (const scalarFieldInst of DataFormUtilities_1.default.getFieldAndAlternates(scalarField)) { content.push(" * This can also be represent as a simple `" + DataFormUtilities_1.default.getFieldTypeDescription(scalarFieldInst.dataType) + "`."); } content.push(""); } content.push(" */"); const subContent = []; content.push("export " + (depth === 0 ? "default " : "") + "interface " + typeName + " {"); content.push(""); const fieldsAdded = {}; if (form.fields && form.fields.length > 0) { form.fields.sort((a, b) => { return a.id.localeCompare(b.id); }); for (const field of form.fields) { let fieldName = field.id ? field.id : field.title; if (!fieldName) { Log_1.default.unexpectedUndefined("Field: " + JSON.stringify(field)); return; } fieldName = Utilities_1.default.sanitizeJavascriptName(fieldName); if (fieldName.length > 0 && !fieldsAdded[fieldName]) { fieldsAdded[fieldName] = true; if (field.description || field.samples) { content.push(" /**"); content.push(" * @remarks"); if (field.description) { FormDefinitionTypeScriptGenerator.appendLongTextWithAsterisks(content, field.description, 60, 3); } if (field.samples) { let samplesAdded = 0; let renderedHeader = false; const samplesUsed = []; for (const samplePath in field.samples) { let sampleArr = field.samples[samplePath]; if (sampleArr && samplesAdded < 3) { const sampleSet = {}; for (const sample of sampleArr) { const sampleVal = JSON.stringify(sample.content); if (!samplesUsed.includes(sampleVal)) { samplesUsed.push(sampleVal); const baseName = StorageUtilities_1.default.getBaseFromName(StorageUtilities_1.default.getLeafName(samplePath)); samplesAdded++; const key = baseName.substring(0, 1).toUpperCase() + baseName.substring(1); if (!sampleSet[key]) { sampleSet[key] = sampleVal; } else { sampleSet[key] = sampleSet[key] + ", " + sampleVal; } } } if (samplesAdded > 0) { if (!renderedHeader) { renderedHeader = true; content.push(" * "); content.push(" * Sample Values:"); } for (const key in sampleSet) { content.push(" * " + Utilities_1.default.humanifyMinecraftName(key) + ": " + this.sanitizeForTable(sampleSet[key])); } } content.push(" *"); } } } content.push(" */"); } let fieldTypeName = typeName + Utilities_1.default.javascriptifyName(Utilities_1.default.ensureFirstCharIsUpperCase(fieldName)); let subForm = field.subForm; if (!subForm && field.subFormId) { subForm = await Database_1.default.ensureFormLoadedByPath(field.subFormId); } if (subForm) { subContent.push("\r\n"); await this.appendType(subForm, subContent, depth + 1, fieldTypeName); } else if (field.choices) { const choices = field.choices; subContent.push("\r\n"); if (choices.length > 0) { await this.appendEnum(form, subContent, choices, depth + 1, fieldTypeName); } } else { fieldTypeName = "object"; } let propLine = " " + fieldName + ": "; if (!field.alternates) { propLine += FormDefinitionTypeScriptGenerator.getTypeScriptFieldTypeDescription(field, fieldTypeName); } else { propLine += FormDefinitionTypeScriptGenerator.getTypeScriptFieldTypeDescription(field, fieldTypeName); for (const altField of field.alternates) { let descri = FormDefinitionTypeScriptGenerator.getTypeScriptFieldTypeDescription(altField, fieldTypeName); if (!propLine.indexOf(descri)) { propLine += " | " + descri; } } } propLine += ";"; content.push(propLine); content.push(""); } } } content.push("}"); content.push(...subContent); } async appendEnum(form, content, choices, depth, typeTitle) { content.push("export enum " + typeTitle + " {"); const choicesAdded = {}; for (let i = 0; i < choices.length; i++) { const choice = choices[i]; let choiceName = choice.id ? choice.id : choice.title; if (!choiceName) { Log_1.default.unexpectedUndefined("Choice: " + JSON.stringify(form)); return; } else { choiceName = choiceName.toString(); const choiceNameJs = Utilities_1.default.javascriptifyName(Utilities_1.default.sanitizeJavascriptName(choiceName), true); if (!choicesAdded[choiceNameJs]) { choicesAdded[choiceNameJs] = true; if (choice.description) { content.push(" /**"); content.push(" * @remarks"); FormDefinitionTypeScriptGenerator.appendLongTextWithAsterisks(content, choice.description, 60, 3); content.push(" */"); } content.push(" " + choiceNameJs + " = " + "`" + choiceName + "`" + (i < choices.length - 1 ? "," : "")); } } } content.push("}"); } static appendLongTextWithAsterisks(content, text, maxLineLength, asteriskSpacing) { const lines = this.splitIntoLines(text, maxLineLength); for (const line of lines) { const asterisks = " ".repeat(asteriskSpacing) + "* "; content.push(asterisks + line); } } static splitIntoLines(value, maxLineLength) { const lines = []; const words = value.split(" "); let curLine = ""; for (let i = 0; i < words.length - 1; i++) { const nextWord = words[i + 1]; if (curLine.length + nextWord.length > maxLineLength) { lines.push(curLine + words[i]); curLine = ""; } else { curLine += words[i] + " "; } } if (curLine.length + words[words.length - 1].length > maxLineLength) { lines.push(curLine); lines.push(words[words.length - 1]); } else { lines.push(curLine + words[words.length - 1]); } return lines; } getValueAsString(value) { if (Array.isArray(value)) { let result = "["; let index = 0; for (const subVal of value) { if (index > 0) { result += ", "; } result += subVal.toString(); index++; } return result + "]"; } return value.toString(); } sanitizeForTable(value) { value = value.replace(/\\r/gi, " "); value = value.replace(/\\t/gi, " "); value = value.replace(/\\n/gi, "<br>"); value = value.replace(/\\"/gi, '"'); value = value.replace(/\r/gi, " "); value = value.replace(/\n/gi, "<br>"); value = value.replace(/ /gi, " "); value = value.replace(/ /gi, " "); value = value.trim(); return value; } static getTypeScriptFieldTypeDescription(field, objectTypeName) { let strDescription = "string"; if (field.choices) { if (field.mustMatchChoices) { strDescription = objectTypeName; } else { strDescription = objectTypeName + "| string"; } } switch (field.dataType) { case IField_1.FieldDataType.int: return "number"; case IField_1.FieldDataType.boolean: return "boolean"; case IField_1.FieldDataType.float: return "number"; case IField_1.FieldDataType.stringEnum: return strDescription; case IField_1.FieldDataType.intEnum: return "number"; case IField_1.FieldDataType.intBoolean: return "boolean"; case IField_1.FieldDataType.number: return "number"; case IField_1.FieldDataType.long: return "number"; case IField_1.FieldDataType.stringLookup: return strDescription; case IField_1.FieldDataType.intValueLookup: return "number"; case IField_1.FieldDataType.point3: return "number[]"; case IField_1.FieldDataType.intPoint3: return "number[]"; case IField_1.FieldDataType.longFormString: return strDescription; case IField_1.FieldDataType.keyedObjectCollection: return "{ [key: string]: any }"; case IField_1.FieldDataType.objectArray: return (objectTypeName ? objectTypeName : "object") + "[]"; case IField_1.FieldDataType.object: return objectTypeName ? objectTypeName : "object"; case IField_1.FieldDataType.stringArray: return "string[]"; case IField_1.FieldDataType.intRange: return "number[]"; case IField_1.FieldDataType.floatRange: return "number[]"; case IField_1.FieldDataType.minecraftFilter: return "jsoncommon.MinecraftFilter"; case IField_1.FieldDataType.percentRange: return "number[]"; case IField_1.FieldDataType.minecraftEventTrigger: return "jsoncommon.MinecraftEventTrigger"; case IField_1.FieldDataType.longFormStringArray: return strDescription + "[]"; case IField_1.FieldDataType.keyedStringCollection: return "{ [key: string]: " + strDescription + " }"; case IField_1.FieldDataType.version: return "string | number[]"; case IField_1.FieldDataType.uuid: return "string"; case IField_1.FieldDataType.keyedBooleanCollection: return "{ [key: string]: boolean }"; case IField_1.FieldDataType.keyedStringArrayCollection: return "{ [key: string]: " + strDescription + "[] }"; case IField_1.FieldDataType.arrayOfKeyedStringCollection: return "{ [key: string]: " + strDescription + "[] }[]"; case IField_1.FieldDataType.keyedKeyedStringArrayCollection: return "{ [key: string]: { [key: string]: string[] } }"; case IField_1.FieldDataType.keyedNumberCollection: return "{ [key: string]: number }"; case IField_1.FieldDataType.keyedNumberArrayCollection: return "{ [key: string]: number[] }"; case IField_1.FieldDataType.numberArray: return "number[]"; case IField_1.FieldDataType.point2: return "number[]"; case IField_1.FieldDataType.localizableString: return "string"; case IField_1.FieldDataType.string: return "string"; case IField_1.FieldDataType.molang: return "string"; case IField_1.FieldDataType.molangArray: return "string[]"; default: return strDescription; } } getMarkdownBookmark(id) { return id.toLowerCase().replace(/ /gi, "-"); } getFormsFromFilter(formsByPath, formsPath, mode) { const filteredList = {}; for (const formPath in formsByPath) { let includeFile = true; if (formPath.indexOf("index") >= 0 || formPath.indexOf("overview") >= 0 || formPath.indexOf("describes") >= 0 || formPath.indexOf("versioned") >= 0) { includeFile = false; } if (includeFile && formPath.toLowerCase().startsWith(formsPath) && formsByPath[formPath] && (formsPath.indexOf("behavior") >= 0 || formPath.indexOf("behavior") < 0) && (formsPath.indexOf("_on") >= 0 || formPath.indexOf("minecraft_on") < 0)) { filteredList[formPath] = formsByPath[formPath]; } } return filteredList; } async loadFormJsonFromFolder(formsByPath, inputFolder, outputFolder) { await inputFolder.load(); const fileList = { files: [], folders: [] }; for (const folderName in inputFolder.folders) { const folder = inputFolder.folders[folderName]; if (folder) { await this.loadFormJsonFromFolder(formsByPath, folder, outputFolder.ensureFolder(folderName)); fileList.folders.push(folderName); } } for (const fileName in inputFolder.files) { const file = inputFolder.files[fileName]; if (file) { await file.loadContent(); const jsonO = StorageUtilities_1.default.getJsonObject(file); if (jsonO) { formsByPath[file.storageRelativePath] = jsonO; } } } } } exports.default = FormDefinitionTypeScriptGenerator; //# sourceMappingURL=../maps/docgen/FormDefinitionTypeScriptGenerator.js.map