UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

1,027 lines 89.6 kB
"use strict"; // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ResourceConsumptionConstraint = void 0; const Project_1 = require("./../app/Project"); const ProjectInfoItem_1 = __importDefault(require("./ProjectInfoItem")); const IInfoItemData_1 = require("./IInfoItemData"); const IProjectInfoData_1 = require("./IProjectInfoData"); const Constants_1 = require("../core/Constants"); const Utilities_1 = __importDefault(require("../core/Utilities")); const Log_1 = __importDefault(require("../core/Log")); const Status_1 = require("../app/Status"); const GeneratorRegistrations_1 = __importStar(require("./registration/GeneratorRegistrations")); const StorageUtilities_1 = __importDefault(require("../storage/StorageUtilities")); const ContentIndex_1 = __importDefault(require("../core/ContentIndex")); const MinecraftUtilities_1 = __importDefault(require("../minecraft/MinecraftUtilities")); const SummaryInfoGenerator_1 = __importDefault(require("./SummaryInfoGenerator")); const HashUtilities_1 = __importDefault(require("../core/HashUtilities")); const Database_1 = __importDefault(require("../minecraft/Database")); const Telemetry_1 = __importDefault(require("../analytics/Telemetry")); const TelemetryConstants_1 = require("../analytics/TelemetryConstants"); const InfoGeneratorTopicUtilities_1 = __importDefault(require("./InfoGeneratorTopicUtilities")); const CrossReferenceIndexGenerator_1 = __importDefault(require("./CrossReferenceIndexGenerator")); const TypesInfoGenerator_1 = __importDefault(require("./TypesInfoGenerator")); const ItemBatchSize = 500; /** * Controls how aggressively generators should constrain resource consumption. * Use this to balance between thoroughness and performance/memory usage. */ var ResourceConsumptionConstraint; (function (ResourceConsumptionConstraint) { /** No constraints - process all data regardless of resource usage */ ResourceConsumptionConstraint[ResourceConsumptionConstraint["none"] = 0] = "none"; /** Medium constraints - apply reasonable limits to prevent excessive resource usage */ ResourceConsumptionConstraint[ResourceConsumptionConstraint["medium"] = 5] = "medium"; })(ResourceConsumptionConstraint || (exports.ResourceConsumptionConstraint = ResourceConsumptionConstraint = {})); class ProjectInfoSet { project; suite; info; items = []; itemsByStoragePath = {}; contentIndex; performAggressiveCleanup = false; constrainResourceConsumption = ResourceConsumptionConstraint.medium; static _generatorsById = {}; _isGenerating = false; _completedGeneration = false; _excludeTests; _pendingGenerateRequests = []; static CommonCsvHeader = "Test,TestId,Type,Item,Message,Data,Path"; static getSuiteFromString(suiteName) { switch (suiteName.toLowerCase()) { case "addon": case "addons": return IProjectInfoData_1.ProjectInfoSuite.cooperativeAddOn; case "sharing": return IProjectInfoData_1.ProjectInfoSuite.sharing; case "sharingstrict": return IProjectInfoData_1.ProjectInfoSuite.sharingStrict; case "currentplatform": return IProjectInfoData_1.ProjectInfoSuite.currentPlatformVersions; default: return IProjectInfoData_1.ProjectInfoSuite.defaultInDevelopment; // default is all infogenerators except cooperative add-on and sharing } } static getSuiteString(suite) { switch (suite) { case IProjectInfoData_1.ProjectInfoSuite.sharing: return "sharing"; case IProjectInfoData_1.ProjectInfoSuite.sharingStrict: return "sharingstrict"; case IProjectInfoData_1.ProjectInfoSuite.cooperativeAddOn: return "addon"; case IProjectInfoData_1.ProjectInfoSuite.currentPlatformVersions: return "currentplatform"; default: return "all"; } } get completedGeneration() { return this._completedGeneration; } /** * Mark generation as complete. Used when results are provided externally * (e.g., from a web worker). */ async markGenerationCompleteAsync() { this._completedGeneration = true; this._isGenerating = false; // Build the itemsByStoragePath index from items array this._rebuildItemsByStoragePath(); // Aggregate features from info items (needed for summary display) this.aggregateFeatures(); // Build contentIndex from project files. // The web worker path doesn't populate the contentIndex (it only streams // validation items), so we must build it on the main thread. this._buildContentIndexFromProject(); // Populate cross-reference and type annotations in the contentIndex. // These are normally populated by generators running in the worker, // but the worker's ContentIndex is not transferred back. await this._populateContentIndexAnnotationsAsync(); } /** * Runs lightweight annotation-producing generators against the contentIndex. * This populates cross-reference annotations (geometry IDs, animation names, * entity/block/item types, etc.) that power autocomplete suggestions. * * Called after _buildContentIndexFromProject() in the web worker path, * since the worker's ContentIndex (with all annotations) is not transferred back. * The project files are already loaded in memory, so this runs quickly. */ async _populateContentIndexAnnotationsAsync() { if (!this.project || !this.contentIndex) { return; } // CrossReferenceIndexGenerator: geometry, animation, animation controller, // render controller, particle, fog, sound, loot table, recipe, biome, // spawn rule, dialogue, function, structure annotations const crossRefGen = new CrossReferenceIndexGenerator_1.default(); await crossRefGen.generate(this.project, this.contentIndex); // TypesInfoGenerator: entity, block, item, feature type annotations const typesGen = new TypesInfoGenerator_1.default(); await typesGen.generate(this.project, this.contentIndex); // Note: generate() returns info items which we discard here — // we already have the info items from the worker stream. } /** * Populates the contentIndex by scanning all project items and their files. * This is needed when the info set was generated via a web worker, which * doesn't build or transfer the content index. */ _buildContentIndexFromProject() { if (!this.project) { return; } const ci = new ContentIndex_1.default(); ci.iteration = new Date().getTime(); for (const projectItem of this.project.items) { if (!projectItem.projectPath) { continue; } const file = projectItem.defaultFile; if (file) { const fileName = file.name; ci.insert(StorageUtilities_1.default.getBaseFromName(fileName), projectItem.projectPath); ci.insert(file.storageRelativePath, projectItem.projectPath); if (file.content && typeof file.content === "string") { const fileExtension = StorageUtilities_1.default.getTypeFromName(fileName); switch (fileExtension) { case "json": ci.parseJsonContent(projectItem.projectPath, file.content); break; case "ts": case "js": case "mjs": ci.parseJsContent(projectItem.projectPath, file.content); break; } } } } this.contentIndex = ci; } /** * Rebuilds the itemsByStoragePath index from the items array. * This is needed when items are set externally (e.g., from a web worker). */ _rebuildItemsByStoragePath() { this.itemsByStoragePath = {}; for (const item of this.items) { if (item.projectItem) { const path = item.projectItem.projectPath; if (path && typeof path === "string") { if (!this.itemsByStoragePath[path]) { this.itemsByStoragePath[path] = []; } this.itemsByStoragePath[path]?.push(item); } } } } get errorAndFailCount() { let count = 0; for (const item of this.items) { if (item.itemType === IInfoItemData_1.InfoItemType.error || item.itemType === IInfoItemData_1.InfoItemType.internalProcessingError || item.itemType === IInfoItemData_1.InfoItemType.testCompleteFail) { count++; } } return count; } get errorFailWarnCount() { let count = 0; for (const item of this.items) { if (item.itemType === IInfoItemData_1.InfoItemType.error || item.itemType === IInfoItemData_1.InfoItemType.warning || item.itemType === IInfoItemData_1.InfoItemType.internalProcessingError || item.itemType === IInfoItemData_1.InfoItemType.testCompleteFail) { count++; } } return count; } get errorFailWarnString() { let str = []; for (const item of this.items) { if (item.itemType === IInfoItemData_1.InfoItemType.error || item.itemType === IInfoItemData_1.InfoItemType.warning || item.itemType === IInfoItemData_1.InfoItemType.internalProcessingError || item.itemType === IInfoItemData_1.InfoItemType.testCompleteFail) { str.push(item.toString()); } } return str.join("\n"); } constructor(project, suite, excludeTests, info, items, index, performAggressiveCleanup) { this.project = project; this.info = info ? info : {}; this.contentIndex = index ? index : new ContentIndex_1.default(); this.performAggressiveCleanup = performAggressiveCleanup ?? false; if (items) { for (const item of items) { let projectItem = undefined; if (item.p) { if (project) { projectItem = project.getItemByProjectPath(item.p); } if (!this.itemsByStoragePath[item.p]) { this.itemsByStoragePath[item.p] = []; } let projectInfoItem = new ProjectInfoItem_1.default(item.iTp, item.gId, item.gIx, item.m, projectItem, item.d, item.iId, item.c, item.p); this.itemsByStoragePath[item.p]?.push(projectInfoItem); this.items.push(projectInfoItem); } else { this.items.push(new ProjectInfoItem_1.default(item.iTp, item.gId, item.gIx, item.m, projectItem, item.d, item.iId, item.c)); } } } if (suite) { this.suite = suite; } else { this.suite = IProjectInfoData_1.ProjectInfoSuite.defaultInDevelopment; } if (index) { if (info) { for (const key in info) { const val = info[key]; if (val && typeof val === "string") { if (ProjectInfoSet.isAggregableFieldName(key)) { index.parseTextContent("inspector", val); } } } } } if (excludeTests) { const excludeTestList = []; for (const excludeTest of excludeTests) { const vals = excludeTest.trim().split(","); for (const val of vals) { const valD = val.toUpperCase().trim(); if (valD.length > 0) { excludeTestList.push(valD); } } } this._excludeTests = excludeTestList; } } static getTopicData(id, index) { if (!Utilities_1.default.isUsableAsObjectKey(id)) { Log_1.default.unsupportedToken(id); throw new Error(); } // First, try to get topic data from form.json file (synchronously if already loaded) const formTopicData = InfoGeneratorTopicUtilities_1.default.getTopicDataSync(id, index); if (formTopicData) { return formTopicData; } // Fall back to generator's getTopicData method if available const gen = ProjectInfoSet._generatorsById[id]; if (gen && typeof gen.getTopicData === "function") { return gen.getTopicData(index); } for (const gen of GeneratorRegistrations_1.default.projectGenerators) { if (gen.id === id) { this._generatorsById[id] = gen; if (typeof gen.getTopicData === "function") { return gen.getTopicData(index); } return undefined; } } return undefined; } static _getLineLocationFromIndex(content, index) { const lineNumber = content.substring(0, index).split("\n").length; const lastLineBreak = content.lastIndexOf("\n", index - 1); const column = index - (lastLineBreak + 1) + 1; return { lineNumber, column }; } static async findLineLocationForItem(content, item) { if (!content) { return undefined; } const directMatch = (matchText) => { if (!matchText) { return undefined; } const index = content.indexOf(matchText); if (index >= 0) { return ProjectInfoSet._getLineLocationFromIndex(content, index); } return undefined; }; const contentLocation = directMatch(item.content); if (contentLocation) { return contentLocation; } if (typeof item.data === "string") { const normalizedData = item.data.startsWith("Relevant line: ") ? item.data.substring("Relevant line: ".length) : item.data; const dataLocation = directMatch(normalizedData); if (dataLocation) { return dataLocation; } } if (item.generatorId) { try { const topicData = await InfoGeneratorTopicUtilities_1.default.getTopicData(item.generatorId, item.generatorIndex); if (topicData?.suggestedLineToken) { const token = topicData.suggestedLineToken; const escapedToken = token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); if (topicData.suggestedLineShouldHaveData && item.data) { const dataStr = String(item.data); const escapedData = dataStr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const combinedPattern = new RegExp(`"${escapedToken}"[^\\n]*${escapedData}|${escapedData}[^\\n]*"${escapedToken}"`, "i"); const combinedMatch = content.match(combinedPattern); if (combinedMatch && combinedMatch.index !== undefined) { return ProjectInfoSet._getLineLocationFromIndex(content, combinedMatch.index); } } const tokenPattern = new RegExp(`"${escapedToken}"\\s*:\\s*[^,}\\]]+`, "i"); const match = content.match(tokenPattern); if (match && match.index !== undefined) { return ProjectInfoSet._getLineLocationFromIndex(content, match.index); } const simpleIndex = content.indexOf(`"${token}"`); if (simpleIndex >= 0) { return ProjectInfoSet._getLineLocationFromIndex(content, simpleIndex); } } } catch { // Ignore errors loading topic data } } if (item.generatorId === "FORMATVER" || item.generatorId?.includes("FORMAT")) { const versionPattern = /"format_version"\s*:\s*"[^"]+"/i; const match = content.match(versionPattern); if (match && match.index !== undefined) { return ProjectInfoSet._getLineLocationFromIndex(content, match.index); } } if (item.generatorId === "ENTITYTYPE" || item.generatorId === "BLOCKTYPE" || item.generatorId === "ITEMTYPE") { const identifierPattern = /"identifier"\s*:\s*"[^"]+"/; const match = content.match(identifierPattern); if (match && match.index !== undefined) { return ProjectInfoSet._getLineLocationFromIndex(content, match.index); } } return undefined; } getCountByType(itemType) { let count = 0; for (const item of this.items) { if (item.itemType === itemType) { count++; } } return count; } getSummaryByType(itemType) { let str = []; for (const item of this.items) { if (str.length < 15 && item.itemType === itemType) { str.push(item.toString()); } } return str.join("\n"); } matchesSuite(generator) { if (this.suite === IProjectInfoData_1.ProjectInfoSuite.defaultInDevelopment && !GeneratorRegistrations_1.TestsToExcludeFromDefaultSuite.includes(generator.id)) { return true; } if (this.suite === IProjectInfoData_1.ProjectInfoSuite.sharing || this.suite === IProjectInfoData_1.ProjectInfoSuite.sharingStrict) { if (generator.id === "SHARING") { return true; } } if (this.suite === IProjectInfoData_1.ProjectInfoSuite.sharingStrict) { if (generator.id === "LANGFILES" || generator.id === "VANDUPES") { return true; } } if (this.suite === IProjectInfoData_1.ProjectInfoSuite.currentPlatformVersions) { if (generator.id === "MINENGINEVER" || generator.id === "BASEGAMEVER" || generator.id === "FORMATVER" || generator.id === "WORLDDATA" || generator.id === "CHKMANIF") { return true; } } if (this.suite === IProjectInfoData_1.ProjectInfoSuite.cooperativeAddOn) { if (generator.id.indexOf("CADDON") >= 0 || generator.id === "PACKSIZE" || generator.id === "STRICT" || generator.id === "TEXTURE" || generator.id === "MINENGINEVER" || generator.id === "WORLDDATA") { return true; } } return false; } /** * Generate info items for the project. * @param force If true, regenerate even if already completed. * @param skipRelationsProcessing If true, skip the processRelations call (useful when relations * have already been processed, e.g., in a combined worker operation). * @param onProgress Optional callback for progress updates (useful for worker thread communication). */ async generateForProject(force, skipRelationsProcessing, onProgress) { if (force === true && this._completedGeneration) { this._completedGeneration = false; this._isGenerating = false; } if (this._completedGeneration) { return; } if (!skipRelationsProcessing) { await this.project?.processRelations(); } if (this._isGenerating) { const pendingGenerate = this._pendingGenerateRequests; const prom = (resolve, reject) => { pendingGenerate.push(resolve); }; await new Promise(prom); } else { this._isGenerating = true; const generationStartTime = Date.now(); if (!this.project) { Log_1.default.throwUnexpectedUndefined("PISGFP"); return; } let baseValidationMessage = "Validating '" + this.project.simplifiedName + "'"; const valOperId = await this.project?.creatorTools.notifyOperationStarted(baseValidationMessage + " (0%)", Status_1.StatusTopic.validation); this.info.summary = undefined; const projGenerators = GeneratorRegistrations_1.default.projectGenerators; const itemGenerators = GeneratorRegistrations_1.default.itemGenerators; const fileGenerators = GeneratorRegistrations_1.default.fileGenerators; const genItems = []; const genItemsByStoragePath = {}; const genContentIndex = new ContentIndex_1.default(); genContentIndex.iteration = new Date().getTime(); await this.project.loc.load(); await Database_1.default.loadVanillaCatalog(); // Preload all topic forms for generators so sync lookups work const allGeneratorIds = [ ...projGenerators.map((g) => g.id), ...itemGenerators.map((g) => g.id), ...fileGenerators.map((g) => g.id), ]; await InfoGeneratorTopicUtilities_1.default.preloadAllForms(allGeneratorIds); if (this.project?.errorState === Project_1.ProjectErrorState.cabinetFileCouldNotBeProcessed) { genItems.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.internalProcessingError, "PROJECTMETA", 500, this.project.simplifiedName + ": " + this.project.errorMessage)); } else { const projectFolder = await this.project.ensureProjectFolder(); const generatorTimings = []; const preProcessStart = Date.now(); await this.preProcessFolder(this.project, projectFolder, genItems, genItemsByStoragePath, genContentIndex, fileGenerators, 0); generatorTimings.push({ phase: "preprocess", id: "preProcessFolder", durationMs: Date.now() - preProcessStart, }); for (let i = 0; i < projGenerators.length; i++) { const gen = projGenerators[i]; if ((!this._excludeTests || !this._excludeTests.includes(gen.id)) && gen && this.matchesSuite(gen)) { const projGenPercent = Math.floor(10 + (i / projGenerators.length) * 20); // 10-30% await this.project?.creatorTools.notifyOperationUpdate(valOperId, baseValidationMessage + " - " + gen.title + " (" + projGenPercent + "%)", Status_1.StatusTopic.validation); // Send progress to worker callback if provided if (onProgress) { const percent = Math.floor(10 + (i / projGenerators.length) * 20); // 10-30% onProgress(`Validating: ${gen.title}`, percent); } GeneratorRegistrations_1.default.configureForSuite(gen, this.suite); try { const genStart = Date.now(); const results = await gen.generate(this.project, genContentIndex); generatorTimings.push({ phase: "project", id: gen.id, durationMs: Date.now() - genStart }); for (const item of results) { this.pushItem(genItems, genItemsByStoragePath, item); } } catch (e) { // V--- add a breakpoint to the line below to catch validator exceptions (1 of 3) ---V genItems.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.internalProcessingError, gen.id, 500, this.project.simplifiedName + ": " + e.message + (e.stack ? " (" + e.stack + ")" : ""))); if (e && (!e.message || !e.message.indexOf || e.message.indexOf("etwork ") < 0)) { Log_1.default.debugAlert(e); } else { this.project?.creatorTools.notifyStatusUpdate("Could not connect to network to retrieve resources for validation. Details: " + e.toString()); } } } } const itemsCopy = this.project.getItemsCopy(); const itemGenTimings = {}; for (const gen of itemGenerators) { itemGenTimings[gen.id] = 0; } const itemLoopStart = Date.now(); for (let i = 0; i < itemsCopy.length; i++) { const pi = itemsCopy[i]; if (i % ItemBatchSize === ItemBatchSize - 1) { const itemPercent = Math.floor(30 + (i / itemsCopy.length) * 50); // 30-80% await this.project?.creatorTools.notifyOperationUpdate(valOperId, baseValidationMessage + " - items (" + itemPercent + "%)", Status_1.StatusTopic.validation); // Send progress to worker callback if provided if (onProgress) { onProgress(`Validating items`, itemPercent); } } if (!pi.isContentLoaded) { await pi.loadContent(); } for (let j = 0; j < itemGenerators.length; j++) { const gen = itemGenerators[j]; if ((!this._excludeTests || !this._excludeTests.includes(gen.id)) && this.matchesSuite(gen)) { GeneratorRegistrations_1.default.configureForSuite(gen, this.suite); try { const itemGenStart = Date.now(); const results = await gen.generate(pi, genContentIndex, { performAggressiveCleanup: this.performAggressiveCleanup, constrainResourceConsumption: this.constrainResourceConsumption, onProgress: onProgress, }); itemGenTimings[gen.id] += Date.now() - itemGenStart; for (const item of results) { this.pushItem(genItems, genItemsByStoragePath, item); } } catch (e) { // V--- add a breakpoint to the line below to catch validator exceptions (2 of 3) ---V genItems.push(new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.internalProcessingError, gen.id, 501, "IP2:" + this.project.simplifiedName + ": " + e.toString())); } } } } generatorTimings.push({ phase: "items-loop", id: "allItemGenerators", durationMs: Date.now() - itemLoopStart }); for (const gen of itemGenerators) { if (itemGenTimings[gen.id] > 0) { generatorTimings.push({ phase: "item", id: gen.id, durationMs: itemGenTimings[gen.id] }); } } const processFolderStart = Date.now(); await this.processFolder(this.project, projectFolder, genItems, genItemsByStoragePath, genContentIndex, fileGenerators, 0); generatorTimings.push({ phase: "postprocess", id: "processFolder", durationMs: Date.now() - processFolderStart, }); // Log timing breakdown generatorTimings.sort((a, b) => b.durationMs - a.durationMs); const totalValidationMs = Date.now() - generationStartTime; Log_1.default.verbose(`[Validation] Total validation time: ${totalValidationMs}ms`); Log_1.default.verbose(`[Validation] Top generators by time:`); for (const t of generatorTimings.slice(0, 20)) { Log_1.default.verbose(`[Validation] ${t.phase.padEnd(12)} ${t.id.padEnd(45)} ${t.durationMs}ms`); } } await this.project?.creatorTools.notifyOperationUpdate(valOperId, baseValidationMessage + " - finishing (95%)", Status_1.StatusTopic.validation); this.addTestSummations(genItems, genItemsByStoragePath, projGenerators, this._excludeTests); this.addTestSummations(genItems, genItemsByStoragePath, itemGenerators, this._excludeTests); this.addTestSummations(genItems, genItemsByStoragePath, fileGenerators, this._excludeTests); genItems.sort((a, b) => { if (a.generatorId !== b.generatorId) { return Utilities_1.default.staticCompare(a.generatorId, b.generatorId); } if (a.generatorIndex !== b.generatorIndex) { return a.generatorIndex - b.generatorIndex; } const aPath = a.projectItemPath; const bPath = b.projectItemPath; if (aPath !== bPath && aPath && bPath) { return Utilities_1.default.staticCompare(aPath, bPath); } if (aPath !== bPath && aPath) { return 1; } if (aPath !== bPath && bPath) { return -1; } if (a.message !== b.message && a.message && b.message) { return Utilities_1.default.staticCompare(a.message, b.message); } if (a.message !== b.message && a.message) { return 1; } if (a.message !== b.message && b.message) { return -1; } if (a.data !== b.data && a.data && b.data && typeof b.data === "string" && typeof a.data === "string") { return Utilities_1.default.staticCompare(a.data, b.data); } if (a.data !== b.data && a.data) { return 1; } if (a.data !== b.data && b.data) { return -1; } return 0; }); this.items = genItems; this.itemsByStoragePath = genItemsByStoragePath; this.contentIndex = genContentIndex; this._completedGeneration = true; this.generateProjectMetaInfo(); const pendingLoad = this._pendingGenerateRequests; this._pendingGenerateRequests = []; this.info.errorCount = this.getCountByType(IInfoItemData_1.InfoItemType.error); this.info.internalProcessingErrorCount = this.getCountByType(IInfoItemData_1.InfoItemType.internalProcessingError); this.info.warningCount = this.getCountByType(IInfoItemData_1.InfoItemType.warning); this.info.testSuccessCount = this.getCountByType(IInfoItemData_1.InfoItemType.testCompleteSuccess); this.info.testFailCount = this.getCountByType(IInfoItemData_1.InfoItemType.testCompleteFail); this.info.testNotApplicableCount = this.getCountByType(IInfoItemData_1.InfoItemType.testCompleteNoApplicableItemsFound); this.info.errorSummary = this.getSummaryByType(IInfoItemData_1.InfoItemType.error); this.info.internalProcessingErrorSummary = this.getSummaryByType(IInfoItemData_1.InfoItemType.internalProcessingError); this.info.warningSummary = this.getSummaryByType(IInfoItemData_1.InfoItemType.warning); this.info.testFailSummary = this.getSummaryByType(IInfoItemData_1.InfoItemType.testCompleteFail); const generationEndTime = Date.now(); this.info.infoGenerationTime = generationEndTime - generationStartTime; if (this.project) { this.info.endToEndGenerationTime = generationEndTime - this.project.creationTime; } if (this.suite === IProjectInfoData_1.ProjectInfoSuite.defaultInDevelopment || this.suite === IProjectInfoData_1.ProjectInfoSuite.sharing || this.suite === IProjectInfoData_1.ProjectInfoSuite.sharingStrict) { this.info.reds = this.getRed(); } this._isGenerating = false; if (valOperId !== undefined) { // End the operation - progress bar disappears immediately await this.project?.creatorTools.notifyOperationEnded(valOperId, "", Status_1.StatusTopic.validation); } if (this.project) { const errorTypes = {}; for (const item of genItems) { if (item.itemType === IInfoItemData_1.InfoItemType.internalProcessingError) { const errorType = item.generatorId || "unknown"; errorTypes[errorType] = (errorTypes[errorType] || 0) + 1; } } const properties = { [TelemetryConstants_1.TelemetryProperties.PROJECT_ITEM_COUNT]: this.project.items.length, [TelemetryConstants_1.TelemetryProperties.INTERNAL_PROCESSING_ERROR_COUNT]: this.info.internalProcessingErrorCount || 0, [TelemetryConstants_1.TelemetryProperties.SUITE_TYPE]: this.suite, }; const errorTypeKeys = Object.keys(errorTypes); if (errorTypeKeys.length > 0) { properties[TelemetryConstants_1.TelemetryProperties.ERROR_TYPES] = errorTypeKeys.join(","); } Telemetry_1.default.trackEvent({ name: TelemetryConstants_1.TelemetryEvents.VALIDATION_COMPLETED, properties, }); } for (const prom of pendingLoad) { prom(undefined); } } } disconnectFromProject() { this.project = undefined; for (const pi of this.items) { pi.disconnect(); } } addTestSummations(genItems, genItemsByStoragePath, generators, excludeTests) { for (const gen of generators) { if ((!excludeTests || !excludeTests.includes(gen.id)) && this.matchesSuite(gen)) { const results = ProjectInfoSet.getItemsInCollection(genItems, gen.id); if (results.length === 0 && !gen.canAlwaysProcess) { this.pushItem(genItems, genItemsByStoragePath, new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.testCompleteNoApplicableItemsFound, gen.id, 2, `No applicable items found for test ${gen.title} (${gen.id})`)); } else { let errorCount = 0; let internalErrorCount = 0; let foundTestVerdict = false; for (const result of results) { if (result.itemType === IInfoItemData_1.InfoItemType.testCompleteFail || result.itemType === IInfoItemData_1.InfoItemType.testCompleteSuccess || result.itemType === IInfoItemData_1.InfoItemType.testCompleteNoApplicableItemsFound) { foundTestVerdict = true; } else if (result.itemType === IInfoItemData_1.InfoItemType.error) { errorCount++; } else if (result.itemType === IInfoItemData_1.InfoItemType.internalProcessingError) { internalErrorCount++; } } if (!foundTestVerdict) { if (errorCount > 0 && internalErrorCount <= 0) { this.pushItem(genItems, genItemsByStoragePath, new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.testCompleteFail, gen.id, 0, `Found ${errorCount} error${errorCount !== 1 ? "s" : ""} in ${gen.title} check`)); } else if (internalErrorCount > 0 && errorCount <= 0) { this.pushItem(genItems, genItemsByStoragePath, new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.testCompleteFail, gen.id, 0, `Found ${internalErrorCount} internal error${internalErrorCount !== 1 ? "s" : ""} in ${gen.title} check. This may be a temporary issue with the test run.`)); } else if (errorCount + internalErrorCount > 0) { this.pushItem(genItems, genItemsByStoragePath, new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.testCompleteFail, gen.id, 0, `Found ${errorCount} error${errorCount !== 1 ? "s" : ""} and ${internalErrorCount} internal error${internalErrorCount !== 1 ? "s" : ""} in ${gen.title} check`)); } else { this.pushItem(genItems, genItemsByStoragePath, new ProjectInfoItem_1.default(IInfoItemData_1.InfoItemType.testCompleteSuccess, gen.id, 1, `${gen.title} completed successfully`)); } } } } } } pushItem(itemSet, itemsByStoragePath, item) { if (item.projectItem && item.projectItem.projectPath && item.itemType !== IInfoItemData_1.InfoItemType.info && item.itemType !== IInfoItemData_1.InfoItemType.featureAggregate) { if (!itemsByStoragePath[item.projectItem.projectPath]) { itemsByStoragePath[item.projectItem.projectPath] = []; } itemsByStoragePath[item.projectItem.projectPath]?.push(item); } itemSet.push(item); } mergeFeatureSetsAndFieldsTo(allFeatureSets, allFields) { if (!this.info || !this.info.featureSets) { return; } for (const str in this.info) { if (str !== "features") { if (Utilities_1.default.isUsableAsObjectKey(str)) { allFields[str] = true; } } } for (const str in allFields) { let inf = this.info; if (ProjectInfoSet.isAggregableFieldName(str) && Utilities_1.default.isUsableAsObjectKey(str)) { if (inf[str] === undefined) { inf[str] = ""; } } } for (const featureName in this.info.featureSets) { if (Utilities_1.default.isUsableAsObjectKey(featureName)) { const myFeature = this.info.featureSets[featureName]; if (myFeature !== undefined) { let allFeature = allFeatureSets[featureName]; if (allFeature === undefined) { allFeature = {}; allFeatureSets[featureName] = allFeature; } for (const measureName in myFeature) { if (Utilities_1.default.isUsableAsObjectKey(measureName)) { const measureVal = myFeature[measureName]; if (measureVal !== undefined) { let allMeasureVal = allFeature[measureName]; if (allMeasureVal === undefined) { allMeasureVal = measureVal; } else { allMeasureVal += measureVal; } allFeature[measureName] = allMeasureVal; } } } } } } } ensureGenerators() { if (!this.info) { return; } if (this.info.summary !== undefined) { return; } this.info.summary = {}; for (let i = 0; i < this.items.length; i++) { const gId = this.items[i].generatorId; const gIndex = this.items[i].generatorIndex; let gen = this.info.summary[gId]; if (gen === undefined) { gen = {}; this.info.summary[gId] = gen; } let genI = gen[gIndex]; if (genI === undefined) { genI = {}; gen[gIndex] = genI; } const topicInfo = ProjectInfoSet.getTopicData(gId, gIndex); if (topicInfo) { genI.title = topicInfo.title; } if (genI.defaultMessage === undefined && this.items[i].message !== genI.title) { genI.defaultMessage = this.items[i].message; } } for (let i = 0; i < this.items.length; i++) { const item = this.items[i]; const gId = item.generatorId; const gIndex = item.generatorIndex; let gen = this.info.summary[gId]; if (gen === undefined) { gen = {}; this.info.summary[gId] = gen; } let genI = gen[gIndex]; if (genI === undefined) { genI = {}; gen[gIndex] = genI; } if (item.itemType !== IInfoItemData_1.InfoItemType.featureAggregate) { switch (item.itemType) { case IInfoItemData_1.InfoItemType.error: genI.errors = genI.errors ? genI.errors + 1 : 1; break; case IInfoItemData_1.InfoItemType.testCompleteFail: genI.testCompleteFails = genI.testCompleteFails ? genI.testCompleteFails + 1 : 1; break; case IInfoItemData_1.InfoItemType.testCompleteSuccess: genI.testCompleteSuccesses = genI.testCompleteSuccesses ? genI.testCompleteSuccesses + 1 : 1; break; case IInfoItemData_1.InfoItemType.warning: genI.warnings = genI.warnings ? genI.warnings + 1 : 1; break; case IInfoItemData_1.InfoItemType.recommendation: genI.recommendations = genI.recommendations ? genI.recommendations + 1 : 1; break; case IInfoItemData_1.InfoItemType.internalProcessingError: genI.internalProcessingErrors = genI.internalProcessingErrors ? genI.internalProcessingErrors + 1 : 1; break; } } if (this.items[i].message === genI.defaultMessage || this.items[i].message === genI.title) { this.items[i].message = undefined; } } } itemToString(item) { let summaryString = item.typeSummaryShort + ": "; summaryString += "[" + item.generatorId + Utilities_1.default.frontPadToLength(item.generatorIndex, 3, "0") + "]"; if (item.shortProjectItemPath) { summaryString += " (" + item.shortProjectItemPath + ")"; } let effectiveMessage = this.getEffectiveMessage(item); if (effectiveMessage.length > 0) { summaryString += " " + effectiveMessage; } if (item.data) { summaryString += ": " + item.data; } const errorContent = item.contentSummary; if (errorContent) { summaryString += " [in " + errorContent + "]"; } return summaryString; } static getExtendedMessageFromData(data, item) { return (this.getEffectiveMessageFromData(data, item) + (item.d ? ": " + item.d : "") + (item.p ? " - " + item.p : "")); } static getEffectiveMessageFromData(data, item) { if (item.m !== undefined) { return item.m; } if (data.info === undefined || data.info.summary === undefined) { return undefined; } let gen = data.info.summary[item.gId]; if (gen === undefined) { return undefined; } let genI = gen[item.gIx]; if (genI === undefined) { return undefined; } return genI.defaultMessage; } getEffectiveMessage(item) { if (item.message !== undefined) { return item.message; } if (this.info === undefined || this.info.summary === undefined) { return ""; } let gen = this.info.summary[item.generatorId]; if (gen === undefined) { return ""; } let genI = gen[item.generatorIndex]; if (genI === undefined) { return ""; } return genI.defaultMessage ? genI.defaultMessage : ""; } shouldIncludeInIndex(data) { if (data.gId === "JSON" || data.gId === "ESLINT") { return false; } return true; } getDataObject(sourceName, sourcePath, sourceHash, isIndexOnly, subsetReports) { const items = []; this.ensureGenerators(); for (let i = 0; i < this.items.length; i++) { const dataObj = this.items[i].dataObject; if (!isIndexOnly || this.shouldIncludeInIndex(dataObj)) { items.push(dataObj); } } Utilities_1.default.encodeObjectWithSequentialRunLengthEncodeUsingNegative(this.contentIndex.data.trie); return { info: this.info, items: items, index: this.contentIndex.data, generatorName: Constants_1.constants.name, suite: this.suite, subsetReports: subsetReports, generatorVersion: Constants_1.constants.version, sourceName: sourceName, sourcePath: sourcePath, sourceHash: sourceHash, }; } static isAggregableFieldName(name) { if (name !== "features" && name !== "summary" && name !== "featureSets" && name !== "defaultIcon") { return true; } return false; } static isAggregableFeatureMeasureName(name) { if (!name.startsWith("#")) { return true; } return false; } static getSummaryCsvHeaderLine(projectInfo, allFeatures) { let csvLine = "Name,Title,Area,"; let fieldNames = []; for (const str in projectInfo) { if (ProjectInfoSet.isAggregableFieldName(str)) { fieldNames.push(str); } } fieldNames = fieldNames.sort(ProjectInfoSet.sortMinecraftFeatures); for (const str of fieldNames) { csvLine += Utilities_1.default.humanifyJsName(str) + ","; } for (const str in projectInfo) { if (ProjectInfoSet.isAggregableFieldName(str)) { fieldNames.push(str); } } for (const featureName in allFeatures) { const feature = allFeatures[featureName]; if (feature) { for (const measureName in feature) { if (ProjectInfoSet.isAggregableFeatureMeasureName(measureName)) { const measure = feature[measureName]; if (meas