UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

972 lines (971 loc) 110 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const Log_1 = __importDefault(require("../core/Log")); const Utilities_1 = __importDefault(require("../core/Utilities")); const IField_1 = require("../dataform/IField"); const Database_1 = __importDefault(require("../minecraft/Database")); const LegacyDocumentationDefinition_1 = __importDefault(require("../minecraft/docs/LegacyDocumentationDefinition")); const StorageUtilities_1 = __importDefault(require("../storage/StorageUtilities")); const ContentIndex_1 = require("../core/ContentIndex"); const DataFormUtilities_1 = __importDefault(require("../dataform/DataFormUtilities")); const EntityTypeDefinition_1 = __importDefault(require("../minecraft/EntityTypeDefinition")); const ICondition_1 = require("../dataform/ICondition"); const FieldUtilities_1 = __importDefault(require("../dataform/FieldUtilities")); const JsonFormExclusionList = ["is_a", "in_the", "_with_"]; const MAX_FORM_DEPTH = 100; const INTERESTING_LIMIT_THRESHOLD = 2147480000; class FormJsonDocumentationGenerator { defsById = {}; defsByTitle = {}; defRefs = {}; defCategories = {}; async updateFormSource(folder, isPreview) { this.defsById = {}; this.defsByTitle = {}; Log_1.default.verbose("[FormJsonDocGen] Starting updateFormSource..."); const metadataFolder = isPreview ? await Database_1.default.loadPreviewMetadataFolder() : await Database_1.default.loadReleaseMetadataFolder(); Log_1.default.verbose("[FormJsonDocGen] Loaded metadata folder"); const schemaFolder = metadataFolder?.ensureFolder("json_schemas"); if (schemaFolder) { Log_1.default.verbose("[FormJsonDocGen] Loading schemas..."); await this.loadSchemas(schemaFolder, "misc"); Log_1.default.verbose("[FormJsonDocGen] Schemas loaded"); } const formJsonFolder = folder.ensureFolder("forms"); await formJsonFolder.ensureExists(); Log_1.default.verbose("[FormJsonDocGen] Exporting JSON schema forms..."); await this.exportJsonSchemaForms(formJsonFolder); Log_1.default.verbose("[FormJsonDocGen] JSON schema forms exported"); // Clear JSON schema data after exporting to free memory - no longer needed this.defsById = {}; this.defsByTitle = {}; this.defRefs = {}; this.defCategories = {}; // Request garbage collection if available (requires --expose-gc flag) if (typeof global !== "undefined" && global.gc) { global.gc(); } Log_1.default.verbose("[FormJsonDocGen] Processing AI Goals..."); const aiGoalsNode = await LegacyDocumentationDefinition_1.default.loadNode("entities", "/Server Entity Documentation/AI Goals/", isPreview); if (aiGoalsNode) { await this.generateFormNodesFromLegacyDocNode(formJsonFolder, aiGoalsNode, "entity"); } Log_1.default.verbose("[FormJsonDocGen] AI Goals done"); const attributesNode = await LegacyDocumentationDefinition_1.default.loadNode("entities", "/Server Entity Documentation/Attributes/", isPreview); if (attributesNode) { await this.generateFormNodesFromLegacyDocNode(formJsonFolder, attributesNode, "entity"); } const propertiesNode = await LegacyDocumentationDefinition_1.default.loadNode("entities", "/Server Entity Documentation/Properties/", isPreview); if (propertiesNode) { await this.generateFormNodesFromLegacyDocNode(formJsonFolder, propertiesNode, "entity"); } const entityComponentsNode = await LegacyDocumentationDefinition_1.default.loadNode("entities", "/Server Entity Documentation/Components/", isPreview); if (entityComponentsNode) { await this.generateFormNodesFromLegacyDocNode(formJsonFolder, entityComponentsNode, "entity"); } const triggersComponentsNode = await LegacyDocumentationDefinition_1.default.loadNode("entities", "/Server Entity Documentation/Triggers/", isPreview); if (triggersComponentsNode) { await this.generateFormNodesFromLegacyDocNode(formJsonFolder, triggersComponentsNode, "entity"); } const filtersComponentsNode = await LegacyDocumentationDefinition_1.default.loadNode("entities", "/Filters/", isPreview); if (filtersComponentsNode) { await this.generateFormNodesFromLegacyDocNode(formJsonFolder, filtersComponentsNode, "entityfilters"); } const entityEventsComponentsNode = await LegacyDocumentationDefinition_1.default.loadNode("entity-events", "/", isPreview); if (entityEventsComponentsNode) { await this.generateFormNodesFromLegacyDocNode(formJsonFolder, entityEventsComponentsNode, "entityevents"); } const blocksComponentsNode = await LegacyDocumentationDefinition_1.default.loadNode("blocks", "/Blocks/Block Components/", isPreview); if (blocksComponentsNode) { await this.generateFormNodesFromLegacyDocNode(formJsonFolder, blocksComponentsNode, "block"); } const schemasNode = await LegacyDocumentationDefinition_1.default.loadNode("schemas", "/Schemas/", isPreview); if (schemasNode) { await this.generateFormNodesFromPseudoSchemaDocs(formJsonFolder, schemasNode, "visual"); } const fogsNode = await LegacyDocumentationDefinition_1.default.loadNode("fogs", "/Fog Definitions/Fog Schema/", isPreview); if (fogsNode) { await this.generateFormNodesFromPseudoSchemaDocs(formJsonFolder, fogsNode, "fogs"); } /* These come from JSON schema now. const biomesNode = await LegacyDocumentationDefinition.loadNode("biomes", "/Schema/", isPreview); if (biomesNode) { await this.generateFormNodesFromNode(formJsonFolder, biomesNode, "biomes"); } */ const featuresNode = await LegacyDocumentationDefinition_1.default.loadNode("features", "/Supported features/", isPreview); if (featuresNode) { const resultForms = await this.generateFormNodesFromPseudoSchemaDocs(formJsonFolder, featuresNode, "features"); if (resultForms) { this.generateSubformsFromFields(formJsonFolder, resultForms, "features"); } } const molangQfNode = await LegacyDocumentationDefinition_1.default.loadNode("molang", "/Query Functions/List of Entity Queries/", isPreview); if (molangQfNode) { await this.generateFormNodesFromLegacyDocNode(formJsonFolder, molangQfNode, "molang"); } const molangMfNode = await LegacyDocumentationDefinition_1.default.loadNode("molang", "/Lexical Structure/Math Functions/", isPreview); if (molangMfNode) { await this.generateFormNodesFromLegacyDocNode(formJsonFolder, molangMfNode, "molang"); } // Clear bulk content caches to free memory Database_1.default.clearBulkContentCaches(); // Request garbage collection if available if (typeof global !== "undefined" && global.gc) { global.gc(); } } async generateSubformsFromFields(formJsonFolder, resultForms, prefix) { const outerForms = []; for (const form of resultForms) { if (form && form.fields) { for (const field of form.fields) { if (field.subForm) { const newForm = { id: field.id, title: field.title, fields: field.subForm.fields, }; outerForms.push(newForm); } } } } for (const form of outerForms) { if (form && form.id) { const name = this.getFormFileName(form.id, form.dataVersion); DataFormUtilities_1.default.mergeFields(form); DataFormUtilities_1.default.fixupFields(form); await this.annotateFormJson(form, name, prefix); await this.mergeToFile(formJsonFolder, name, form, prefix); } } return outerForms; } async generateFormNodesFromPseudoSchemaDocs(formJsonFolder, node, prefix) { if (!node.description && !node.examples) { return undefined; } const formStack = []; let formStackIndex = -1; const outerForms = []; const objectSkippedAt = []; let lastField = []; let ignoreNextObject = 0; let integrateNextProperty = false; let integrateNextNextProperty = false; let nodeSet = node.description; if (!nodeSet && node.examples) { nodeSet = []; for (const examp of node.examples) { nodeSet.push(...examp.text); } } for (const docLine of nodeSet) { let docLineMod = docLine; let commentStr = undefined; integrateNextNextProperty = false; let commentIndex = docLine.indexOf(" //"); if (commentIndex > 0) { commentStr = docLine.substring(commentIndex + 3); docLineMod = docLine.substring(0, commentIndex); } const docLineTrim = docLineMod.replace(/ /gi, "").trim(); const startQuote = docLineMod.indexOf('"'); let endQuote = docLineMod.lastIndexOf('"'); const endCompare = docLineMod.lastIndexOf(">"); let mainStr = undefined; if (startQuote >= 0 && endQuote > startQuote) { if (endCompare === endQuote + 1) { endQuote = endCompare; } mainStr = docLineMod.substring(startQuote + 1, endQuote); } if (docLineTrim.endsWith(":{") && !mainStr) { // this is the pattern from schemas.json const firstColon = docLineTrim.indexOf(":"); if (firstColon >= 0) { formStackIndex++; for (let i = formStackIndex; i < MAX_FORM_DEPTH; i++) { lastField[i] = undefined; objectSkippedAt[i] = 0; } const form = { id: FormJsonDocumentationGenerator.cleanForId(docLineTrim.substring(0, firstColon)), fields: [], }; outerForms.push(form); formStack[formStackIndex] = form; const secondColon = docLineTrim.indexOf(":", firstColon + 1); if (secondColon > firstColon) { const verStr = docLineTrim.substring(firstColon + 1, secondColon); if (Utilities_1.default.isVersionString(verStr)) { form.dataVersion = Utilities_1.default.normalizeVersionString(verStr); } } } } else if (docLineTrim === "{" && formStackIndex < 0) { // this is the pattern from fogs.json, features.json (only one form/object, typically) - handle the outer level form formStackIndex++; for (let i = formStackIndex; i < MAX_FORM_DEPTH; i++) { lastField[i] = undefined; objectSkippedAt[i] = 0; } const form = { id: FormJsonDocumentationGenerator.cleanForId((prefix ? prefix : "obj") + (outerForms.length > 0 ? outerForms.length + 1 : "")), fields: [], }; outerForms.push(form); formStack[formStackIndex] = form; } else if (docLineTrim === "{") { if (ignoreNextObject > 0) { ignoreNextObject--; objectSkippedAt[formStackIndex]++; } else { const lastFieldStack = lastField[formStackIndex]; formStackIndex++; for (let i = formStackIndex; i < MAX_FORM_DEPTH; i++) { lastField[i] = undefined; objectSkippedAt[i] = 0; } if (lastFieldStack && !lastFieldStack.subForm) { const form = { id: undefined, fields: [] }; formStack[formStackIndex] = form; lastFieldStack.subForm = form; } } } else if (docLineTrim === "}" && formStackIndex >= 0) { if (objectSkippedAt[formStackIndex] > 0) { objectSkippedAt[formStackIndex]--; } else { formStackIndex--; } } if (formStackIndex >= 0 && formStack[formStackIndex] && mainStr) { let fieldDefinition = undefined; if (docLineTrim.startsWith('int"')) { fieldDefinition = { dataType: IField_1.FieldDataType.int, id: FormJsonDocumentationGenerator.cleanForId(mainStr), title: Utilities_1.default.humanifyMinecraftName(mainStr), }; } else if (docLineTrim.startsWith('bool"')) { fieldDefinition = { dataType: IField_1.FieldDataType.boolean, id: FormJsonDocumentationGenerator.cleanForId(mainStr), title: Utilities_1.default.humanifyMinecraftName(mainStr), }; } else if (docLineTrim.startsWith('string"')) { fieldDefinition = { dataType: IField_1.FieldDataType.string, id: FormJsonDocumentationGenerator.cleanForId(mainStr), title: Utilities_1.default.humanifyMinecraftName(mainStr), }; } else if (docLineTrim.startsWith('molang"')) { fieldDefinition = { dataType: IField_1.FieldDataType.molang, id: FormJsonDocumentationGenerator.cleanForId(mainStr), title: Utilities_1.default.humanifyMinecraftName(mainStr), }; } else if (docLineTrim.startsWith('array"')) { fieldDefinition = { dataType: IField_1.FieldDataType.stringArray, id: FormJsonDocumentationGenerator.cleanForId(mainStr), title: Utilities_1.default.humanifyMinecraftName(mainStr), }; const firstArrow = mainStr.indexOf("<"); const secondArrow = mainStr.indexOf(">"); const lastFieldStack = lastField[formStackIndex - 1]; if (firstArrow >= 0 && secondArrow > firstArrow) { if (lastFieldStack && (lastFieldStack.dataType === IField_1.FieldDataType.object || lastFieldStack.dataType === IField_1.FieldDataType.objectArray)) { lastFieldStack.dataType = IField_1.FieldDataType.keyedObjectCollection; lastFieldStack.keyDescription = mainStr; } } integrateNextNextProperty = true; ignoreNextObject++; } else if (docLineTrim.startsWith('enumerated_value"')) { const firstArrow = mainStr.indexOf("<"); const secondArrow = mainStr.indexOf(">"); if (firstArrow >= 0 && secondArrow > firstArrow) { const fieldId = mainStr.substring(0, firstArrow); const choiceStr = mainStr.substring(firstArrow + 1, secondArrow); const choices = choiceStr.split(","); const choiceSet = []; for (const choice of choices) { if (choice.length > 0) { choiceSet.push({ id: FormJsonDocumentationGenerator.cleanForId(choice) }); } } fieldDefinition = { dataType: IField_1.FieldDataType.string, id: fieldId, choices: choiceSet, title: Utilities_1.default.humanifyMinecraftName(mainStr), }; } else { fieldDefinition = { dataType: IField_1.FieldDataType.string, id: mainStr, title: Utilities_1.default.humanifyMinecraftName(mainStr), }; } } else if (docLineTrim.startsWith('object"')) { let fieldDataType = IField_1.FieldDataType.object; const firstArrow = mainStr.indexOf("<"); const secondArrow = mainStr.indexOf(">"); const lastFieldStack = lastField[formStackIndex - 1]; const curFieldStack = lastField[formStackIndex]; if (firstArrow >= 0 && secondArrow > firstArrow) { const subStr = mainStr.substring(firstArrow, secondArrow - firstArrow); if (subStr.indexOf("array") >= 0 && curFieldStack) { if (!curFieldStack.alternates) { curFieldStack.alternates = []; } const newField = { id: curFieldStack.id, dataType: IField_1.FieldDataType.objectArray, }; curFieldStack.alternates.push(newField); lastField[formStackIndex] = newField; } else { if (lastFieldStack && (lastFieldStack.dataType === IField_1.FieldDataType.object || lastFieldStack.dataType === IField_1.FieldDataType.objectArray)) { lastFieldStack.dataType = IField_1.FieldDataType.keyedObjectCollection; lastFieldStack.keyDescription = mainStr; } ignoreNextObject++; } } else { fieldDefinition = { dataType: fieldDataType, id: mainStr, title: Utilities_1.default.humanifyMinecraftName(mainStr), }; } } else if (docLineTrim.startsWith('version"')) { fieldDefinition = { dataType: IField_1.FieldDataType.version, id: mainStr, title: Utilities_1.default.humanifyMinecraftName(mainStr), }; } if (fieldDefinition) { if (integrateNextProperty && fieldDefinition.id.indexOf("<") >= 0) { if (fieldDefinition.dataType === IField_1.FieldDataType.molang) { const lastFieldStack = lastField[formStackIndex]; if (lastFieldStack) { lastFieldStack.dataType = IField_1.FieldDataType.molangArray; } } integrateNextProperty = false; } else { integrateNextProperty = false; if (commentStr) { fieldDefinition.description = commentStr.trim(); } if (docLineTrim.indexOf(":opt") >= 0) { fieldDefinition.isRequired = false; } else { fieldDefinition.isRequired = true; } lastField[formStackIndex] = fieldDefinition; formStack[formStackIndex].fields.push(fieldDefinition); } } if (integrateNextNextProperty) { integrateNextProperty = true; } } } for (const form of outerForms) { if (form && form.id) { const name = this.getFormFileName(form.id, form.dataVersion); DataFormUtilities_1.default.mergeFields(form); DataFormUtilities_1.default.fixupFields(form); await this.annotateFormJson(form, name, prefix); await this.mergeToFile(formJsonFolder, name, form, prefix); } } return outerForms; } static cleanForId(id) { if (!id) { return ""; } id = Utilities_1.default.removeQuotes(id.replace(/\`/gi, "")); let parenStart = id.indexOf(" ("); let parenEnd = id.indexOf(")"); if (parenStart > 0 && parenEnd > parenStart) { id = id.substring(0, parenStart) + id.substring(parenEnd + 1); id = id.trim(); } return id; } async generateFormJson(inputFolder, outputFolder) { await outputFolder.deleteAllFolderContents(); await this.generateFormJsonFromFolder(inputFolder, outputFolder); } async generateFormJsonFromFolder(inputFolder, outputFolder) { await outputFolder.ensureExists(); if (!inputFolder.isLoaded) { await inputFolder.load(); } const fileList = { files: [], folders: [] }; for (const folderName in inputFolder.folders) { const folder = inputFolder.folders[folderName]; if (folder) { try { await this.generateFormJsonFromFolder(folder, outputFolder.ensureFolder(folderName)); fileList.folders.push(folderName); } catch (e) { Log_1.default.error("Error processing folder " + folderName + ": " + e); if (e instanceof Error && e.stack) { Log_1.default.error(e.stack); } } } } for (const fileName in inputFolder.files) { const file = inputFolder.files[fileName]; try { if (file) { await file.loadContent(); const jsonO = StorageUtilities_1.default.getJsonObject(file); if (jsonO) { const outputFile = outputFolder.ensureFile(fileName); fileList.files.push(fileName); await this.finalizeJsonForm(jsonO, outputFile); } // Unload file content after extracting JSON to save memory during bulk processing file.unload(); } } catch (e) { Log_1.default.error("Error processing file " + fileName + ": " + e); if (e instanceof Error && e.stack) { Log_1.default.error(e.stack); } } } fileList.files.sort(); fileList.folders.sort(); const indexFile = outputFolder.ensureFile("index.json"); indexFile.setContent(Utilities_1.default.consistentStringifyTrimmed(fileList)); await indexFile.saveContent(); } async finalizeJsonForm(formObj, outputFile) { if (!formObj.generated_doNotEdit && !formObj.generatedFromSchema_doNotEdit && formObj.id) { const id = formObj.id.replace(/:/gi, "_").replace(/\./gi, "_"); await outputFile.loadContent(); const originalNode = StorageUtilities_1.default.getJsonObject(outputFile); await this.annotateFormJson(formObj, id, outputFile.parentFolder.name, originalNode); } if (formObj.generatedFromSchema_doNotEdit) { this.mergeOntoForm(formObj, formObj.generatedFromSchema_doNotEdit); } if (formObj.generated_doNotEdit) { this.mergeOntoForm(formObj, formObj.generated_doNotEdit); } formObj.generated_doNotEdit = undefined; formObj.generatedFromSchema_doNotEdit = undefined; // Normalize any legacy numeric dataType values to string equivalents FieldUtilities_1.default.normalizeFormFieldDataTypes(formObj); outputFile.setContent(JSON.stringify(formObj, undefined, 2)); await outputFile.saveContent(); } mergeOntoForm(targetForm, generatedFormToMergeOn) { if (!targetForm.description || targetForm.description === "") { targetForm.description = generatedFormToMergeOn.description; } if (!targetForm.technicalDescription || targetForm.technicalDescription === "") { targetForm.technicalDescription = generatedFormToMergeOn.technicalDescription; } if (!targetForm.title || targetForm.title === "") { targetForm.title = generatedFormToMergeOn.title; } if (targetForm.samples) { for (const samplePath in generatedFormToMergeOn.samples) { targetForm.samples[samplePath] = generatedFormToMergeOn.samples[samplePath]; } } else { targetForm.samples = generatedFormToMergeOn.samples; } if (!targetForm.id) { targetForm.id = generatedFormToMergeOn.id; } if (!targetForm.note) { targetForm.note = generatedFormToMergeOn.note; } if (!targetForm.note2) { targetForm.note2 = generatedFormToMergeOn.note2; } if (!targetForm.note3) { targetForm.note3 = generatedFormToMergeOn.note3; } if (!targetForm.restrictions) { targetForm.restrictions = generatedFormToMergeOn.restrictions; } if (!targetForm.requires) { targetForm.requires = generatedFormToMergeOn.requires; } if (!targetForm.scalarFieldUpgradeName && generatedFormToMergeOn.scalarFieldUpgradeName) { targetForm.scalarFieldUpgradeName = generatedFormToMergeOn.scalarFieldUpgradeName; targetForm.scalarField = undefined; // you can either have a scalarFieldUpgradeName, or a scalarField, but not both. defer to scalarFieldUpgradeName } else if (!targetForm.scalarField) { targetForm.scalarField = generatedFormToMergeOn.scalarField; } if (!targetForm.customField) { targetForm.customField = generatedFormToMergeOn.customField; } if (!targetForm.isDeprecated) { targetForm.isDeprecated = generatedFormToMergeOn.isDeprecated; } if (!targetForm.versionIntroduced) { targetForm.versionIntroduced = generatedFormToMergeOn.versionIntroduced; } if (!targetForm.versionDeprecated) { targetForm.versionDeprecated = generatedFormToMergeOn.versionDeprecated; } if (!targetForm.tags) { targetForm.tags = generatedFormToMergeOn.tags; } if (!targetForm.isInternal) { targetForm.isInternal = generatedFormToMergeOn.isInternal; } if (!targetForm.dataVersion) { targetForm.dataVersion = generatedFormToMergeOn.dataVersion; } // Guard: only proceed with field merging if formToMergeOn has fields to merge if (!generatedFormToMergeOn.fields || generatedFormToMergeOn.fields.length === 0) { // Nothing to merge from generated content - preserve target fields as-is return; } if (targetForm.fields && targetForm.fields.length === 0) { // Deep clone to avoid shared references targetForm.fields = JSON.parse(JSON.stringify(generatedFormToMergeOn.fields)); } else { const formFields = {}; if (!targetForm.fields) { // Deep clone to avoid shared references targetForm.fields = JSON.parse(JSON.stringify(generatedFormToMergeOn.fields)); } else { for (const targetField of targetForm.fields) { formFields[targetField.id] = targetField; } for (const generatedMergeOnField of generatedFormToMergeOn.fields) { const targetField = formFields[generatedMergeOnField.id]; if (!targetField) { // Deep clone when adding new field to avoid shared references targetForm.fields.push(JSON.parse(JSON.stringify(generatedMergeOnField))); } else { if (targetField.isRemoved) { formFields[generatedMergeOnField.id] = undefined; const newFieldArr = []; for (const updatedField of targetForm.fields) { if (updatedField.id !== generatedMergeOnField.id) { newFieldArr.push(updatedField); } } targetForm.fields = newFieldArr; } else { targetField.samples = generatedMergeOnField.samples; if (targetField.defaultValue === undefined) { targetField.defaultValue = generatedMergeOnField.defaultValue; } // Only use generated dataType if override doesn't specify one if (targetField.dataType === undefined && generatedMergeOnField.dataType !== undefined) { targetField.dataType = generatedMergeOnField.dataType; } // subForm handling: // - If targetField (override) has a subForm, it takes precedence (full replacement) // - If targetField has no subForm but mergeOnField (generated) does, use generated if (!targetField.subForm && generatedMergeOnField.subForm && !targetField.subFormId) { // No override subForm - use generated subForm (deep clone) targetField.subForm = JSON.parse(JSON.stringify(generatedMergeOnField.subForm)); } // If targetField.subForm exists, keep it as-is (override wins) // mergeSubForm on targetField (override): explicitly merge generated subForm into override's subForm // Use this when you want to keep override fields AND add generated fields if (targetField.mergeSubForm && generatedMergeOnField.subForm && !targetField.subFormId) { if (!targetField.subForm) { targetField.subForm = { fields: [], }; } this.mergeOntoForm(targetField.subForm, generatedMergeOnField.subForm); // Clear the mergeSubForm flag after processing targetField.mergeSubForm = undefined; } if (generatedMergeOnField.subFormId) { targetField.subFormId = generatedMergeOnField.subFormId; targetField.subForm = undefined; // you can either have a subFormId, or a subForm, but not both. defer to subFormId } // Only use generated alternates if override doesn't have any // Deep clone to avoid shared references between forms if (!targetField.alternates && generatedMergeOnField.alternates) { targetField.alternates = JSON.parse(JSON.stringify(generatedMergeOnField.alternates)); } if (!targetField.description) { targetField.description = generatedMergeOnField.description; } if (!targetField.title) { targetField.title = generatedMergeOnField.title; } if (!targetField.versionDeprecated) { targetField.versionDeprecated = generatedMergeOnField.versionDeprecated; } if (!targetField.versionIntroduced) { targetField.versionIntroduced = generatedMergeOnField.versionIntroduced; } if (!targetField.humanifyValues) { targetField.humanifyValues = generatedMergeOnField.humanifyValues; } if (!targetField.tags) { targetField.tags = generatedMergeOnField.tags; } if (!targetField.minLength) { targetField.minLength = generatedMergeOnField.minLength; } if (!targetField.maxLength) { targetField.maxLength = generatedMergeOnField.maxLength; } if (!targetField.minValue) { targetField.minValue = generatedMergeOnField.minValue; } if (!targetField.priority) { targetField.priority = generatedMergeOnField.priority; } if (!targetField.note) { targetField.note = generatedMergeOnField.note; } if (!targetField.note2) { targetField.note2 = generatedMergeOnField.note2; } if (!targetField.note3) { targetField.note3 = generatedMergeOnField.note3; } if (!targetField.fixedLength) { targetField.fixedLength = generatedMergeOnField.fixedLength; } if (!targetField.retainIfEmptyOrDefault) { targetField.retainIfEmptyOrDefault = generatedMergeOnField.retainIfEmptyOrDefault; } if (!targetField.allowedKeys) { targetField.allowedKeys = generatedMergeOnField.allowedKeys; } if (!targetField.objectArrayTitleFieldKey) { targetField.objectArrayTitleFieldKey = generatedMergeOnField.objectArrayTitleFieldKey; } if (!targetField.objectArrayToSubFieldKey) { targetField.objectArrayToSubFieldKey = generatedMergeOnField.objectArrayToSubFieldKey; } if (!targetField.matchObjectArrayLengthToSubFieldLength) { targetField.matchObjectArrayLengthToSubFieldLength = generatedMergeOnField.matchObjectArrayLengthToSubFieldLength; } if (!targetField.matchObjectArrayToSubFieldKey) { targetField.matchObjectArrayToSubFieldKey = generatedMergeOnField.matchObjectArrayToSubFieldKey; } if (!targetField.keyDescription) { targetField.keyDescription = generatedMergeOnField.keyDescription; } if (!targetField.maxValue) { targetField.maxValue = generatedMergeOnField.maxValue; } if (!targetField.suggestedMinValue) { targetField.suggestedMinValue = generatedMergeOnField.suggestedMinValue; } if (!targetField.suggestedMaxValue) { targetField.suggestedMaxValue = generatedMergeOnField.suggestedMaxValue; } if (!targetField.isRequired) { targetField.isRequired = generatedMergeOnField.isRequired; } if (targetField.dataType === undefined) { targetField.dataType = generatedMergeOnField.dataType; } if (generatedMergeOnField.choices) { if (!targetField.choices) { targetField.choices = []; } for (const mergeOnChoice of generatedMergeOnField.choices) { let foundChoice = false; for (const targetChoice of targetField.choices) { if (targetChoice.id === mergeOnChoice.id) { foundChoice = true; if (!targetChoice.title) { targetChoice.title = mergeOnChoice.title; } if (!targetChoice.description) { targetChoice.description = mergeOnChoice.description; } if (!targetChoice.isDeprecated) { targetChoice.isDeprecated = mergeOnChoice.isDeprecated; } if (!targetChoice.iconImage) { targetChoice.iconImage = mergeOnChoice.iconImage; } if (!targetChoice.versionIntroduced) { targetChoice.versionIntroduced = mergeOnChoice.versionIntroduced; } if (!targetChoice.versionDeprecated) { targetChoice.versionDeprecated = mergeOnChoice.versionDeprecated; } break; } } if (!foundChoice) { targetField.choices.push(mergeOnChoice); } } } if (!targetField.validity) { targetField.validity = generatedMergeOnField.validity; } } } } } } } async exportJsonSchemaForms(formJsonFolder) { const keys = Object.keys(this.defsByTitle); Log_1.default.verbose(`[FormJsonDocGen] exportJsonSchemaForms: Processing ${keys.length} definitions`); let processed = 0; for (const key of keys) { if (this.getIsStandaloneSchemaFile(key)) { // Log progress every 50 forms to avoid verbose output if (processed % 50 === 0) { Log_1.default.verbose(`[FormJsonDocGen] Processing form ${processed + 1}/${keys.length}...`); } await this.processAndExportJsonSchemaNode(formJsonFolder, key); processed++; } } Log_1.default.verbose(`[FormJsonDocGen] exportJsonSchemaForms: Done, processed ${processed} total`); } getIsStandaloneSchemaFile(keyOrTitle) { if (keyOrTitle.startsWith("#/definitions/")) { keyOrTitle = keyOrTitle.substring(14); } let formNode = this.defsById[keyOrTitle]; if (!formNode) { formNode = this.defsByTitle[keyOrTitle]; } if (formNode && formNode.properties && Object.keys(formNode.properties).length > 0) { if (formNode.title) { keyOrTitle = formNode.title; } return !this.isDisallowedSchemaFile(keyOrTitle) && keyOrTitle.indexOf(" - ") < 0 && keyOrTitle.indexOf("_-_") < 0; } return false; } isDisallowedSchemaFile(key) { if (key.indexOf("struct ") >= 0) { return true; } return false; } getFormPathForJsonSchemaForm(schemaKey) { if (schemaKey.startsWith("#/definitions/")) { schemaKey = schemaKey.substring(14); } const form = this.getDefinitionFromId(schemaKey); let title = form && form.title ? form.title : schemaKey; let name = this.getFormFileName(title); const category = this.defCategories[schemaKey]; if (category) { name = category + "/" + name; } return name; } async processAndExportJsonSchemaNode(formJsonFolder, title) { const formNode = await this.getJsonFormFromJsonSchemaKey(title); if (formNode && formNode.fields.length > 0) { if (title.indexOf("omponents") >= 0) { if (formNode.fields) { for (const field of formNode.fields) { if (field.subForm) { const name = this.getFormFileName(field.id); const category = this.defCategories[title]; field.subForm.id = field.id; await this.annotateFormJson(field.subForm, name, category); await this.mergeToFile(formJsonFolder, name, field.subForm, category, true); } } } } const category = this.defCategories[title]; let schemaTitle = category && title.startsWith(category + ".") ? title.substring(category.length + 1) : title; schemaTitle = this.getVersionlessString(schemaTitle); const name = this.getFormFileName(schemaTitle); let matchesExclusion = false; for (const exclusion of JsonFormExclusionList) { if (name.indexOf(exclusion) >= 0) { matchesExclusion = true; break; } } if (!matchesExclusion) { await this.annotateFormJson(formNode, name, category); await this.mergeToFile(formJsonFolder, name, formNode, category, true); } } } getFormFileNameBase(key) { key = key.toLowerCase(); if (key.startsWith("struct_") || key.startsWith("struct ")) { key = key.substring(7); } if ((key.startsWith("enum_") || key.startsWith("enum ")) && key.indexOf("num_property") < 0) { key = key.substring(5); } key = key.replace("sharedtypes", ""); key = StorageUtilities_1.default.sanitizePathBasic(key); return key; } getVersionlessString(key) { let verStart = key.indexOf(" v1."); if (verStart >= 0) { const nextSpace = key.indexOf(" ", verStart + 4); if (nextSpace >= 0) { key = key.substring(0, verStart) + key.substring(nextSpace + 1); } else { key = key.substring(0, verStart); } } return key; } async getJsonFormFromJsonSchemaKey(keyName) { let rootNodeName = undefined; let rootNodeNameVersionless = undefined; let rootNode = undefined; const keyVersionless = this.getVersionlessString(keyName); // attempt to get the latest version of a component by sorting on the node name e.g., minecraft:item v1.21.60 should sort later than minecraft:item v1.21.40 // though we should replace this with a more sophisticated sorter for version :-/ for (const candidateKey in this.defsByTitle) { const candidateKeyVersionless = this.getVersionlessString(candidateKey); if (candidateKeyVersionless === keyVersionless) { if (!rootNodeName || (candidateKey.localeCompare(rootNodeName) > 0 && candidateKeyVersionless === rootNodeNameVersionless)) { rootNodeName = candidateKey; rootNodeNameVersionless = candidateKeyVersionless; rootNode = this.defsByTitle[candidateKey]; } } } for (const key in this.defsById) { if (key.indexOf(keyName) >= 0) { const keyVersionless = this.getVersionlessString(key); if (!rootNodeName || (key.localeCompare(rootNodeName) > 0 && keyVersionless === rootNodeNameVersionless)) { rootNodeName = key; rootNodeNameVersionless = keyVersionless; rootNode = this.defsByTitle[key]; } } } if (rootNode === undefined) { return; } if (rootNodeName !== keyName) { return; } return await this.getJsonFormFromJsonSchemaDefinition(rootNode, keyName, undefined, 0); } async getJsonFormFromJsonSchemaDefinition(node, nodeName, fieldList, depth = 0) { if (depth > MAX_FORM_DEPTH) { Log_1.default.debug(`[FormJsonDocGen] Max depth ${MAX_FORM_DEPTH} exceeded for form: ${nodeName}`); return undefined; } const fields = []; if (node.properties) { for (const propName in node.properties) { const propNode = node.properties[propName]; if (propNode && typeof propNode !== "boolean") { const field = await this.getFieldFromJsonPropertyNode(propNode, propName, fieldList, depth + 1); if (field) { fields.push(field); } } } } if (node.required) { for (const propName of node.required) { for (const field of fields) { if (field.id === propName) { field.isRequired = true; break; } } } } if (!nodeName) { if (node.title) { nodeName = node.title; } else { nodeName = ""; } } const docForm = { id: FormJsonDocumentationGenerator.humanifyId(nodeName), title: FormJsonDocumentationGenerator.humanifySchemaTag(nodeName), description: node.description ? FormJsonDocumentationGenerator.humanifyText(node.description) : undefined, fields: fields, }; return docForm; } static humanifySchemaTag(name) { name = FormJsonDocumentationGenerator.humanifyText(name); const firstPeriod = name.indexOf("."); if (firstPeriod >= 2) { const category = Utilities_1.default.humanifyMinecraftName(name.substring(0, firstPeriod)); const adjustedHumanify = Utilities_1.default.humanifyMinecraftName(name.substring(0, firstPeriod)) + " " + Utilities_1.default.humanifyMinecraftName(name.substring(firstPeriod + 1)); return adjustedHumanify.replace(category + " " + category, category); } return Utilities_1.default.humanifyMinecraftName(name); } async loadSchemas(schemaFolder, categoryName) { if (!schemaFolder.isLoaded) { await schemaFolder.load(); } for (const fileName in schemaFolder.files) { const file = schemaFolder.files[fileName]; if (file && file.type === "json" && file.name !== "index.json") { if (!fil