@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
972 lines (971 loc) • 110 kB
JavaScript
"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