@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
555 lines (553 loc) • 28.7 kB
JavaScript
;
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