@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
1,106 lines • 63.6 kB
JavaScript
#!/usr/bin/env node
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const CartoApp_js_1 = require("./../app/CartoApp.js");
const process = require("process");
const NodeStorage_js_1 = require("../local/NodeStorage.js");
const StorageUtilities_js_1 = require("../storage/StorageUtilities.js");
const Constants_js_1 = require("../core/Constants.js");
const ProjectInfoSet_js_1 = require("../info/ProjectInfoSet.js");
const IInfoItemData_js_1 = require("../info/IInfoItemData.js");
const LocalEnvironment_js_1 = require("../local/LocalEnvironment.js");
const IProjectItemData_js_1 = require("../app/IProjectItemData.js");
const Log_js_1 = require("../core/Log.js");
const ClUtils_js_1 = require("./ClUtils.js");
const threads_1 = require("threads");
const ContentIndex_js_1 = require("../core/ContentIndex.js");
const inquirer = require("inquirer");
const IGalleryItem_js_1 = require("../app/IGalleryItem.js");
const ProjectUtilities_js_1 = require("../app/ProjectUtilities.js");
const Project_js_1 = require("../app/Project.js");
const ProjectExporter_js_1 = require("../app/ProjectExporter.js");
const Utilities_js_1 = require("../core/Utilities.js");
const IProjectInfoData_js_1 = require("../info/IProjectInfoData.js");
const MinecraftUtilities_js_1 = require("../minecraft/MinecraftUtilities.js");
const ServerManager_js_1 = require("../local/ServerManager.js");
if (typeof btoa === "undefined") {
// @ts-ignore
global.btoa = function (str) {
return Buffer.from(str, "binary").toString("base64");
};
}
if (typeof atob === "undefined") {
// @ts-ignore
global.atob = function (b64Encoded) {
return Buffer.from(b64Encoded, "base64").toString("binary");
};
}
CartoApp_js_1.default.hostType = CartoApp_js_1.HostType.toolsNodejs;
const MAX_LINES_PER_CSV_FILE = 500000;
const ERROR_INIT_ERROR = 44;
const ERROR_VALIDATION_INTERNALPROCESSINGERROR = 53;
const ERROR_VALIDATION_TESTFAIL = 56;
const ERROR_VALIDATION_ERROR = 57;
const program = new commander_1.Command();
let carto;
const projectStarts = [];
let localEnv;
let mode;
let type;
let newName;
let newDescription;
let template;
let creator;
let serverHostPort;
let suite;
let exclusionList;
let outputType;
let serverFeatures;
let serverTitle;
let serverDomainName;
let serverMessageOfTheDay;
let serverCandidateAdminPasscode;
let serverCandidateDisplayReadOnlyPasscode;
let serverCandidateFullReadOnlyPasscode;
let serverCandidateUpdateStatePasscode;
let serverRunOnce;
let aggregateReportsAfterValidation = false;
let threads = 8;
let errorLevel;
let force = false;
let executionTaskType = ClUtils_js_1.TaskType.noCommand;
program
.name("mct")
.description("Minecraft Creator Tools v" + Constants_js_1.constants.version)
.option("-i, --input-folder [path to folder]", "Path to the input folder. If not specified, the current working directory is used.")
.option("-if, --input-file [path to file]", "Path to the input MCWorld, MCTemplate, MCPack, MCAddon or other zip file.")
.option("-o, --output-folder <path to folder>", "Path to the output project folder. If not specified, the current working directory + 'out' is used.", "out")
.option("-afs, --additional-files [path to file]", "Comma-separated list of additional files to add to projects.")
.option("-of, --output-file [path to file]", "Path to the export file, if applicable for the command you are using.")
.option("-ot, --output-type [output type]", "Type of output, if applicable for the command you are using.")
.option("-updatepc, --update-passcode [update passcode]", "Sets update passcode.")
.option("-bp, --behavior-pack <behavior pack uuid>", "Adds a set of behavior pack UUIDs as references for any worlds that are updated.")
.option("-rp, --resource-pack <resource pack uuid>", "Adds a set of resources pack UUIDs as references for any worlds that are updated.")
.option("-betaapis, --beta-apis", "Ensures that the Beta APIs experiment is set for any worlds that are updated.")
.option("-no-betaapis, --no-beta-apis", "Removes the Beta APIs experiment if set.")
.option("-f, --force", "Force any updates.")
.option("-single, --single", "When pointed at at a folder via -i, force that folder to be processed as a single project.")
.option("-editor", "Ensures that the world is an Editor world.")
.option("-once", "When running as a server, only process one request and then shutdown.", false)
.option("-no-editor", "Removes that the world is an editor.")
.option("--threads [thread count]", "Targeted number of threads to use.")
.option("-show, --display-only", "Whether to only show messages, vs. output report files.")
.option("-lv, --log-verbose", "Whether to show verbose log messages.");
program.addHelpText("before", "\x1b[32m┌─────┐\x1b[0m");
program.addHelpText("before", "\x1b[32m│ ▄ ▄ │\x1b[0m Minecraft Creator Tools (preview) command line");
program.addHelpText("before", "\x1b[32m│ ┏▀┓ │\x1b[0m See " + Constants_js_1.constants.homeUrl + " for more info.");
program.addHelpText("before", "\x1b[32m└─────┘\x1b[0m");
program.addHelpText("before", " ");
program
.command("world")
.alias("w")
.description("Displays/sets world settings within a folder.")
.addArgument(new commander_1.Argument("[mode]", "If set to 'set', will update the world with a set of properties that are specified."))
.action((modeIn) => {
mode = modeIn;
executionTaskType = ClUtils_js_1.TaskType.world;
});
program
.command("create")
.alias("c")
.description("Creates a new Minecraft project")
.addArgument(new commander_1.Argument("[name]", "Desired project name"))
.addArgument(new commander_1.Argument("[template]", "Template name"))
.addArgument(new commander_1.Argument("[creator]", "Creator name"))
.addArgument(new commander_1.Argument("[description]", "Project description"))
.action((nameIn, templateIn, creatorIn, descriptionIn) => {
newName = nameIn;
template = templateIn;
creator = creatorIn;
newDescription = descriptionIn;
executionTaskType = ClUtils_js_1.TaskType.create;
});
program
.command("minecrafteulaandprivacypolicy")
.alias("eula")
.description("See the Minecraft End User License Agreement.")
.action(() => {
executionTaskType = ClUtils_js_1.TaskType.minecraftEulaAndPrivacyPolicy;
});
program
.command("serve")
.alias("srv")
.description("Hosts a web service and site that can manage a dedicated server and/or validation.")
.addArgument(new commander_1.Argument("[features]", "Specifies a title the Minecraft Http Server on.").choices([
"all",
"allwebservices",
"basicwebservices",
"dedicatedserver",
]))
.addArgument(new commander_1.Argument("[domain]", "Specifies which URL domain this server is hosted on."))
.addArgument(new commander_1.Argument("[host-port]", "Specifies which core port to host the Minecraft Http Server on."))
.addArgument(new commander_1.Argument("[title]", "Specifies a title the Minecraft Http Server on."))
.addArgument(new commander_1.Argument("[motd]", "Specifies a message of the day for the server."))
.action(async (features, domain, hostPort, title, motd) => {
if (features) {
switch (features.toLowerCase()) {
case "all":
serverFeatures = ServerManager_js_1.ServerManagerFeatures.all;
break;
case "allwebservices":
serverFeatures = ServerManager_js_1.ServerManagerFeatures.allWebServices;
break;
case "basicwebservices":
serverFeatures = ServerManager_js_1.ServerManagerFeatures.basicWebServices;
break;
}
}
serverHostPort = hostPort;
serverTitle = title;
serverDomainName = domain;
serverMessageOfTheDay = motd;
executionTaskType = ClUtils_js_1.TaskType.serve;
});
program
.command("setserverprops")
.alias("setsrv")
.description("Updates default properties for the Minecraft Http Server.")
.addArgument(new commander_1.Argument("[domain]", "Specifies which URL domain this server is hosted on."))
.addArgument(new commander_1.Argument("[host-port]", "Specifies which core port to host the Minecraft Http Server on."))
.addArgument(new commander_1.Argument("[title]", "Specifies a title the Minecraft Http Server on."))
.addArgument(new commander_1.Argument("[motd]", "Specifies a message of the day for the server."))
.action(async (domain, hostPort, title, motd) => {
serverHostPort = hostPort;
serverTitle = title;
serverDomainName = domain;
serverMessageOfTheDay = motd;
executionTaskType = ClUtils_js_1.TaskType.setServerProperties;
});
program
.command("info")
.alias("i")
.description("Displays information about the current project.")
.action(() => {
executionTaskType = ClUtils_js_1.TaskType.info;
});
program
.command("validate")
.alias("val")
.description("Validate the current project.")
.addArgument(new commander_1.Argument("[suite]", "Specifies the type of validation suite to run.")
.choices(["all", "default", "addon", "currentplatform", "main"])
.default("main", "main (same as default) - runs most available validation tests."))
.addArgument(new commander_1.Argument("[exclusions]", "Specifies a comma-separated list of tests to exclude, e.g., PATHLENGTH,PACKSIZE"))
.addArgument(new commander_1.Argument("[aggregateReports]", "Whether to aggregate reports across projects at the end of the run.")
.choices(["true", "false"])
.default("false", "false - does not aggregate reports at the end of processing."))
.action((suiteIn, exclusionListIn, aggregateReportsIn) => {
suite = suiteIn;
exclusionList = exclusionListIn;
executionTaskType = ClUtils_js_1.TaskType.validate;
if (aggregateReportsIn === "aggregate" ||
aggregateReportsIn === "true" ||
aggregateReportsIn === "1" ||
aggregateReportsIn === "t") {
aggregateReportsAfterValidation = true;
}
else {
aggregateReportsAfterValidation = false;
}
});
program
.command("aggregatereports")
.alias("aggr")
.description("Aggregates exported metadata about projects.")
.action(() => {
executionTaskType = ClUtils_js_1.TaskType.aggregateReports;
});
program
.command("version")
.alias("ver")
.description("Shows version and general application information")
.action(() => {
executionTaskType = ClUtils_js_1.TaskType.version;
});
program.parse(process.argv);
const options = program.opts();
localEnv = new LocalEnvironment_js_1.default(true);
let sm;
if (options.force) {
force = true;
}
if (options.threads) {
try {
let tc = parseInt(options.threads);
if (tc > 0) {
threads = tc;
console.log("Using " + threads + " threads.");
}
}
catch (e) {
Log_js_1.default.error("Could not process the threads parameter: " + e);
}
}
if (options.outputType && typeof options.outputType === "string") {
switch (options.outputType.toLowerCase().trim()) {
case "noreports":
outputType = ClUtils_js_1.OutputType.noReports;
}
}
if (options.displayOnly) {
localEnv.displayInfo = true;
if (options.outputFolder === "out") {
options.outputFolder = undefined;
}
}
else if (options.outputFolder === "out") {
if (executionTaskType !== ClUtils_js_1.TaskType.serve) {
Log_js_1.default.message("Outputting full results to the `out` folder.");
}
localEnv.displayInfo = true;
}
if (options.adminPasscode) {
serverCandidateAdminPasscode = options.adminPasscode;
}
if (options.updatePasscode) {
serverCandidateUpdateStatePasscode = options.updatePasscode;
}
if (options.displayPasscode) {
serverCandidateDisplayReadOnlyPasscode = options.displayPasscode;
}
if (options.once || options.Once) {
serverRunOnce = true;
}
if (options.fullReadOnlyPasscode) {
serverCandidateFullReadOnlyPasscode = options.fullReadOnlyPasscode;
}
if (options.logVerbose) {
localEnv.displayVerbose = true;
}
(async () => {
carto = ClUtils_js_1.default.getCarto(localEnv);
if (!carto) {
return;
}
sm = new ServerManager_js_1.default(localEnv, carto);
sm.runOnce = serverRunOnce;
await carto.load();
carto.onStatusAdded.subscribe(ClUtils_js_1.default.handleStatusAdded);
await loadProjects();
if (serverCandidateDisplayReadOnlyPasscode ||
serverCandidateUpdateStatePasscode ||
serverCandidateAdminPasscode ||
serverCandidateFullReadOnlyPasscode) {
await setPasscode(serverCandidateDisplayReadOnlyPasscode, serverCandidateFullReadOnlyPasscode, serverCandidateUpdateStatePasscode, serverCandidateAdminPasscode);
}
switch (executionTaskType) {
case ClUtils_js_1.TaskType.info:
await displayInfo();
break;
case ClUtils_js_1.TaskType.version:
await displayVersion();
break;
case ClUtils_js_1.TaskType.validate:
await validate();
break;
case ClUtils_js_1.TaskType.aggregateReports:
await aggregateReports();
break;
case ClUtils_js_1.TaskType.serve:
hookInput();
await applyServerProps();
await serve();
break;
case ClUtils_js_1.TaskType.create:
try {
for (const projectStart of projectStarts) {
if (projectStart) {
await create(ClUtils_js_1.default.createProject(carto, projectStart), projectStarts.length <= 1);
}
}
}
catch (e) {
errorLevel = ERROR_INIT_ERROR;
console.error("Error creating a project. " + e.toString());
}
break;
case ClUtils_js_1.TaskType.minecraftEulaAndPrivacyPolicy:
await minecraftEulaAndPrivacyPolicy();
break;
case ClUtils_js_1.TaskType.world:
await setAndDisplayAllWorlds();
break;
}
})();
async function loadProjects() {
if (!carto || !carto.ensureLocalFolder) {
throw new Error("Not properly configured.");
}
const additionalFiles = [];
if (options.inputFile && options.inputFolder) {
throw new Error("Cannot specify both an input file and an input folder.");
}
if (options.additionalFiles && typeof options.additionalFiles === "string") {
const paths = options.additionalFiles.split(",");
for (const path of paths) {
additionalFiles.push(path);
}
}
if (options.inputFile) {
const inputFolderPath = StorageUtilities_js_1.default.getFolderPath(options.inputFile);
const inputFileName = StorageUtilities_js_1.default.getLeafName(options.inputFile);
if (!inputFileName || inputFileName.length < 2 || !inputFolderPath || inputFolderPath.length < 2) {
throw new Error("Could not process file with path: `" + options.inputFile + "`");
}
const containingFolder = carto.ensureLocalFolder(inputFolderPath);
const file = containingFolder.ensureFile(inputFileName);
const fileExists = await file.exists();
if (!fileExists) {
throw new Error("Could not find file with path: `" + options.inputFile + "`.");
}
const fileName = StorageUtilities_js_1.default.getLeafName(options.inputFile);
const mainProject = {
ctorProjectName: fileName,
localFilePath: options.inputFile,
accessoryFiles: additionalFiles.slice(),
};
projectStarts.push(mainProject);
return;
}
const workFolder = await ClUtils_js_1.default.getMainWorkFolder(executionTaskType, options.inputFolder, options.outputFolder);
const name = StorageUtilities_js_1.default.getLeafName(workFolder.fullPath);
let isMultiLevelMultiProject = true;
let foundASubProject = false;
// multilevel multi project is a one-level folder hierarchy of subfolders with zip and
// metadata files in them.
// root folder must be empty
// foo\coolgame.zip <--- this is the project
// foo\coolgame.data.json
// bar\swellgame.zip <--- this is another project
// bar\swellgame.data.json
let storageItemCount = 0;
if (workFolder.fileCount > 0) {
for (const subFileName in workFolder.files) {
const file = workFolder.files[subFileName];
if (file && !StorageUtilities_js_1.default.isFileStorageItem(file)) {
isMultiLevelMultiProject = false;
break;
}
}
}
if (!options.single) {
if (isMultiLevelMultiProject) {
for (const subFolderName in workFolder.folders) {
const subFolder = workFolder.folders[subFolderName];
if (subFolder) {
await subFolder.load();
for (const subFileName in subFolder.files) {
const subFile = subFolder.files[subFileName];
if (subFile) {
if (StorageUtilities_js_1.default.isFileStorageItem(subFile)) {
storageItemCount++;
}
let typeFromName = StorageUtilities_js_1.default.getTypeFromName(subFileName);
if (!StorageUtilities_js_1.default.isFileStorageItem(subFile) &&
typeFromName !== "json" &&
typeFromName !== "csv" &&
typeFromName !== "" &&
typeFromName !== "html") {
isMultiLevelMultiProject = false;
continue;
}
}
}
}
}
}
if (storageItemCount < 2) {
isMultiLevelMultiProject = false;
}
if (isMultiLevelMultiProject) {
console.log("Working across subfolders with projects at '" + workFolder.fullPath + "'");
for (const subFolderName in workFolder.folders) {
const subFolder = workFolder.folders[subFolderName];
if (subFolder && !subFolder.errorStatus) {
await subFolder.load();
for (const fileName in subFolder.files) {
const file = subFolder.files[fileName];
if (file && StorageUtilities_js_1.default.isFileStorageItem(file)) {
const ps = { ctorProjectName: file.name, accessoryFiles: additionalFiles.slice() };
let baseName = StorageUtilities_js_1.default.getBaseFromName(file.name);
if (subFolder.files[baseName + ".data.json"]) {
ps.accessoryFiles?.push(baseName + ".data.json");
}
let lastDash = baseName.lastIndexOf("-");
if (lastDash > 0) {
baseName = baseName.substring(0, lastDash);
if (subFolder.files[baseName + ".data.json"]) {
ps.accessoryFiles?.push(baseName + ".data.json");
}
}
ps.localFilePath = file.fullPath;
projectStarts.push(ps);
}
}
}
}
if (projectStarts.length > 0) {
return;
}
}
else {
// "children of folder multi project" scans for either zip files in the root folder and/or
// every subfolder is a separate project
let isChildrenOfFolderMultiProject = true;
for (const fileName in workFolder.files) {
const file = workFolder.files[fileName];
if (file) {
if (!StorageUtilities_js_1.default.isFileStorageItem(file)) {
isChildrenOfFolderMultiProject = false;
continue;
}
else {
foundASubProject = true;
}
}
}
for (const folderName in workFolder.folders) {
if (MinecraftUtilities_js_1.default.pathLooksLikePackName(folderName) ||
MinecraftUtilities_js_1.default.pathLooksLikePackContainerName(folderName)) {
isChildrenOfFolderMultiProject = false;
continue;
}
}
if (isChildrenOfFolderMultiProject) {
for (const folderName in workFolder.folders) {
const folder = workFolder.folders[folderName];
if (folder && !folder.errorStatus) {
await folder.load(true);
if (folder.files["manifest.json"] ||
folder.files["pack_manifest.json"] ||
folder.folders["content"] ||
folder.folders["Content"] ||
folder.folders["world_template"] ||
folder.folders["behavior_packs"]) {
foundASubProject = true;
}
if (StorageUtilities_js_1.default.isMinecraftInternalFolder(folder)) {
isChildrenOfFolderMultiProject = false;
continue;
}
}
}
}
if (!foundASubProject) {
isChildrenOfFolderMultiProject = false;
}
if (isChildrenOfFolderMultiProject) {
console.log("Working across subfolders/packages at '" + workFolder.fullPath + "'");
for (const fileName in workFolder.files) {
const file = workFolder.files[fileName];
if (file && StorageUtilities_js_1.default.isFileStorageItem(file)) {
const mainProject = {
ctorProjectName: file.name,
accessoryFiles: additionalFiles.slice(),
};
mainProject.localFilePath = file.fullPath;
projectStarts.push(mainProject);
}
}
for (const folderName in workFolder.folders) {
const folder = workFolder.folders[folderName];
if (folder && !folder.errorStatus && folder.name !== "out") {
await folder.load();
if (folder.folderCount > 0) {
const mainProject = {
ctorProjectName: folder.name,
accessoryFiles: additionalFiles.slice(),
};
mainProject.localFolderPath = folder.fullPath;
projectStarts.push(mainProject);
}
}
}
if (projectStarts.length > 0) {
return;
}
}
}
}
// OK, just assume this folder is a big single project then.
const mainProject = { ctorProjectName: name, accessoryFiles: additionalFiles.slice() };
mainProject.localFolderPath = workFolder.fullPath;
projectStarts.push(mainProject);
}
async function applyServerProps() {
if (localEnv) {
await localEnv.load();
if (serverHostPort) {
localEnv.serverHostPort = serverHostPort;
}
if (serverTitle) {
localEnv.serverTitle = serverTitle;
}
if (serverDomainName) {
localEnv.serverDomainName = serverDomainName;
}
if (serverMessageOfTheDay) {
localEnv.serverMessageOfTheDay = serverMessageOfTheDay;
}
await localEnv.save();
}
}
async function doExit() {
if (sm) {
sm.stopWebServer();
}
if (!errorLevel) {
process.exitCode = errorLevel;
}
}
function hookInput() {
process.stdin.setEncoding("utf-8");
process.stdin.on("data", async function (data) {
if (data.startsWith("exit") || data.startsWith("stop")) {
await doExit();
}
});
}
async function displayVersion() {
console.log("\n" + Constants_js_1.constants.name + " Tools");
console.log("Version: " + Constants_js_1.constants.version);
if (carto && carto.local) {
const local = carto.local;
console.log("\n");
console.log("Machine user data path: " + local.userDataPath);
console.log("Machine app data path: " + local.localAppDataPath);
console.log("Minecraft path: " + local.minecraftPath);
console.log("Server working path: " + local.serversPath);
console.log("Environment prefs path: " + local.envPrefsPath);
}
console.log("\n");
console.log(Constants_js_1.constants.copyright);
console.log(Constants_js_1.constants.disclaimer);
console.log("\n");
}
async function setPasscode(displayReadOnlyPasscode, fullReadOnlyPasscode, updateStatePasscode, adminPasscode) {
if (localEnv === undefined) {
throw new Error();
}
await localEnv.load();
if (localEnv.displayReadOnlyPasscode === undefined ||
localEnv.adminPasscode === undefined ||
localEnv.fullReadOnlyPasscode === undefined ||
localEnv.updateStatePasscode === undefined) {
await localEnv.setDefaults();
}
if (adminPasscode) {
localEnv.adminPasscode = adminPasscode;
}
if (displayReadOnlyPasscode) {
localEnv.displayReadOnlyPasscode = displayReadOnlyPasscode;
}
if (fullReadOnlyPasscode) {
localEnv.fullReadOnlyPasscode = fullReadOnlyPasscode;
}
if (updateStatePasscode) {
localEnv.updateStatePasscode = updateStatePasscode;
}
await localEnv.save();
}
function getFriendlyPasscode(str) {
if (str.length >= 4) {
str = str.toUpperCase();
return str.substring(0, 4) + "-" + str.substring(4);
}
return str;
}
async function stop() {
process.exit();
}
async function minecraftEulaAndPrivacyPolicy() {
if (!localEnv) {
throw new Error();
}
await localEnv.load();
const questions = [];
console.log("To download the Minecraft Bedrock Dedicated Server, you must agree to the Minecraft End User License Agreement and Privacy Policy.\n");
console.log(" Minecraft End User License Agreement: https://minecraft.net/eula");
console.log(" Minecraft Privacy Policy: https://go.microsoft.com/fwlink/?LinkId=521839\n");
questions.push({
type: "confirm",
default: false,
name: "minecraftEulaAndPrivacyPolicy",
message: "I agree to the Minecraft End User License Agreement and Privacy Policy",
});
const answers = await inquirer.prompt(questions);
const iaccept = answers["minecraftEulaAndPrivacyPolicy"];
if (iaccept === true || iaccept === false) {
localEnv.iAgreeToTheMinecraftEndUserLicenseAgreementAndPrivacyPolicyAtMinecraftDotNetSlashEula = iaccept;
await localEnv.save();
}
}
async function displayInfo() {
for (const projectStart of projectStarts) {
if (projectStart && carto) {
const project = ClUtils_js_1.default.createProject(carto, projectStart);
await project.inferProjectItemsFromFiles();
console.log("Project name: " + project.name);
console.log("Project description: " + project.description);
const bpFolder = await project.getDefaultBehaviorPackFolder();
if (bpFolder === null) {
console.log("No default behavior pack.");
}
else {
console.log("Default behavior pack folder: " + bpFolder.storageRelativePath);
}
const rpFolder = await project.getDefaultResourcePackFolder();
if (rpFolder === null) {
console.log("No default resource pack.");
}
else {
console.log("Default resource pack folder: " + rpFolder.storageRelativePath);
}
const itemsCopy = project.getItemsCopy();
for (let i = 0; i < itemsCopy.length; i++) {
const item = itemsCopy[i];
console.log("=== " + item.typeTitle + ": " + item.projectPath);
if (item.isWorld) {
await setAndDisplayWorld(item, false);
}
}
const pis = project.infoSet;
await pis.generateForProject();
for (let i = 0; i < pis.items.length; i++) {
const item = pis.items[i];
if (item.itemType !== IInfoItemData_js_1.InfoItemType.testCompleteFail && item.itemType !== IInfoItemData_js_1.InfoItemType.testCompleteSuccess) {
console.log(pis.itemToString(item));
}
}
}
}
}
async function setAndDisplayAllWorlds() {
const isEnsure = mode === "set";
let ofName;
if (options.outputFolder) {
ofName = StorageUtilities_js_1.default.getLeafName(options.outputFolder);
if (ofName) {
ofName = StorageUtilities_js_1.default.canonicalizeName(ofName);
}
}
for (const projectStart of projectStarts) {
if (projectStart && carto) {
const project = ClUtils_js_1.default.createProject(carto, projectStart);
await project.inferProjectItemsFromFiles();
let setWorld = false;
const itemsCopy = project.getItemsCopy();
for (const item of itemsCopy) {
if (item.isWorld) {
let shouldProcess = true;
if (item.projectPath && ofName) {
const name = StorageUtilities_js_1.default.canonicalizeName(StorageUtilities_js_1.default.getBaseFromName(StorageUtilities_js_1.default.getLeafName(item.projectPath)));
shouldProcess = ofName === name;
}
if (shouldProcess) {
await setAndDisplayWorld(item, isEnsure);
setWorld = true;
}
else {
await setAndDisplayWorld(item, false);
}
}
}
// create a new world
if (isEnsure && !setWorld && options.outputFolder) {
const wcf = await project.ensureWorldContainer();
if (wcf && project.projectFolder) {
// create a new folder for the world.
let destF = project.projectFolder;
const targetName = destF.name;
// if only an output folder is specified, put the world there
// if an input and an output folder is specified, put the world at a subfolder of the input folder.
if (options.outputFolder) {
let targetFolder = options.outputFolder;
if (options.inputFolder && targetFolder.startsWith(options.inputFolder)) {
targetFolder = targetFolder.substring(options.inputFolder.length);
}
if (targetFolder.length > 2) {
destF = await wcf.ensureFolderFromRelativePath(StorageUtilities_js_1.default.ensureEndsDelimited(targetFolder));
}
}
if (destF) {
let path = destF.getFolderRelativePath(project.projectFolder);
if (path) {
path = StorageUtilities_js_1.default.ensureEndsWithDelimiter(StorageUtilities_js_1.default.absolutize(path));
const pi = project.ensureItemByProjectPath(path, IProjectItemData_js_1.ProjectItemStorageType.folder, targetName, IProjectItemData_js_1.ProjectItemType.worldFolder);
await pi.ensureFolderStorage();
await setAndDisplayWorld(pi, true);
}
}
}
}
}
}
}
async function setAndDisplayWorld(item, isSettable) {
if (item.isWorld) {
const mcworld = await item.getManager();
if (mcworld) {
await mcworld.load(false);
if (isSettable) {
if (mcworld.name === "" && mcworld.storageFullPath) {
mcworld.name = StorageUtilities_js_1.default.getBaseFromName(StorageUtilities_js_1.default.getLeafName(mcworld.storageFullPath));
}
console.log("Updating mcworld at '" + mcworld.storageFullPath + "'");
const levelDat = mcworld.ensureLevelData();
let hasSet = false;
if (options.betaApis !== undefined && options.betaApis !== levelDat.betaApisExperiment) {
levelDat.betaApisExperiment = options.betaApis;
console.log("Set beta APIs to " + options.betaApis);
hasSet = true;
}
if (options.editor !== undefined && options.editor !== levelDat.isCreatedInEditor) {
levelDat.isCreatedInEditor = options.editor;
console.log("Set is editor to " + options.editor);
hasSet = true;
}
if (options.dataDrivenItems !== undefined && options.dataDrivenItems !== levelDat.dataDrivenItemsExperiment) {
levelDat.dataDrivenItemsExperiment = options.dataDrivenItems;
console.log("Set data driven items to " + options.dataDrivenItems);
hasSet = true;
}
if (options.behaviorPack !== undefined) {
mcworld.ensureBehaviorPacksFromString(options.behaviorPack);
}
if (options.resourcePack !== undefined) {
mcworld.ensureBehaviorPacksFromString(options.resourcePack);
}
if (hasSet) {
await mcworld.save();
}
}
console.log("World name: " + mcworld.name);
console.log("World path: " + item.projectPath);
if (mcworld.betaApisExperiment !== undefined) {
console.log("Beta APIs: " + mcworld.betaApisExperiment);
}
if (mcworld.levelData) {
if (mcworld.levelData.dataDrivenItemsExperiment !== undefined) {
console.log("Data Driven items (holiday experimental): " + mcworld.levelData.dataDrivenItemsExperiment);
}
}
}
}
}
async function displayServerProps() {
if (!localEnv) {
return;
}
let domainName = localEnv.serverDomainName;
if (!domainName) {
domainName = "(unspecified; used for display purposes only)";
}
let port = localEnv.serverHostPort;
if (!port) {
port = 80;
}
let title = localEnv.serverTitle;
if (!title) {
title = "(unspecified; used for display purposes only)";
}
let motd = localEnv.serverMessageOfTheDay;
if (!motd) {
motd = "(unspecified; used for display purposes only)";
}
console.log("Server host domain name: " + domainName);
console.log("Server port: " + port);
console.log("Server title: " + title);
console.log("Server message of the day: " + motd);
}
async function validate() {
if (!carto || !localEnv) {
return;
}
const projectList = [];
const pool = (0, threads_1.Pool)(() => (0, threads_1.spawn)(new threads_1.Worker("./TaskWorker"), {
timeout: 25000,
}), threads);
const suiteConst = suite;
const exclusionListConst = exclusionList;
const aggregateReportsAfterValidationConst = aggregateReportsAfterValidation;
const localEnvConst = localEnv;
for (let i = 0; i < projectStarts.length; i++) {
const ps = projectStarts[i];
pool.queue(async (doTask) => {
const result = await doTask({
task: ClUtils_js_1.TaskType.validate,
project: ps,
arguments: {
suite: suiteConst,
exclusionList: exclusionListConst,
outputMci: aggregateReportsAfterValidationConst || outputType === ClUtils_js_1.OutputType.noReports ? true : false,
outputType: outputType,
},
outputFolder: options.outputFolder,
inputFolder: options.inputFolder,
displayInfo: localEnvConst.displayInfo,
displayVerbose: localEnvConst.displayVerbose,
force: force,
});
if (result !== undefined) {
if (typeof result === "string") {
if (ps) {
Log_js_1.default.error(ps.ctorProjectName + " error: " + result);
}
}
else {
for (const metaState of result) {
// clear out icons since the aggregation won't need them, and it should save memory.
if (metaState.infoSetData && metaState.infoSetData.info && metaState.infoSetData.info["defaultIcon"]) {
metaState.infoSetData.info["defaultIcon"] = undefined;
}
projectList.push(metaState);
const infoSet = metaState.infoSetData;
if (infoSet) {
const items = infoSet.items;
if (items) {
for (const item of items) {
if (item.iTp === IInfoItemData_js_1.InfoItemType.internalProcessingError) {
console.error("Internal Processing Error: " + ProjectInfoSet_js_1.default.getEffectiveMessageFromData(infoSet, item));
setErrorLevel(ERROR_VALIDATION_INTERNALPROCESSINGERROR);
}
else if (item.iTp === IInfoItemData_js_1.InfoItemType.testCompleteFail && !options.outputFolder) {
console.error("Test Fail: " + ProjectInfoSet_js_1.default.getEffectiveMessageFromData(infoSet, item));
setErrorLevel(ERROR_VALIDATION_TESTFAIL);
}
else if (item.iTp === IInfoItemData_js_1.InfoItemType.error && !options.outputFolder) {
console.error("Error: " + ProjectInfoSet_js_1.default.getEffectiveMessageFromData(infoSet, item));
setErrorLevel(ERROR_VALIDATION_ERROR);
}
}
}
}
}
}
}
});
}
await pool.settled();
await pool.terminate();
if (aggregateReportsAfterValidation) {
Log_js_1.default.message("Aggregating reports across " + projectList.length + " projects.");
await saveAggregatedReports(projectList);
}
}
function setErrorLevel(newErrorLevel) {
if (errorLevel === undefined || newErrorLevel < errorLevel) {
errorLevel = newErrorLevel;
process.exitCode = newErrorLevel;
}
}
async function aggregateReports() {
let inpFolder = await ClUtils_js_1.default.getMainWorkFolder(executionTaskType, options.inputFolder, options.outputFolder);
const allFeatureSets = {};
const allFields = {};
const projectList = [];
if (!inpFolder) {
Log_js_1.default.message("No main input folder was specified.");
}
await inpFolder.load();
let projectsLoaded = 0;
for (const fileName in inpFolder.files) {
let file = inpFolder.files[fileName];
if (file && StorageUtilities_js_1.default.getTypeFromName(fileName) === "json") {
await file.loadContent(true);
let jsonO = await StorageUtilities_js_1.default.getJsonObject(file);
if (jsonO.info && jsonO.items && jsonO.generatorName !== undefined && jsonO.generatorVersion !== undefined) {
const pis = new ProjectInfoSet_js_1.default(undefined, undefined, undefined, jsonO.info, jsonO.items);
let suite = undefined;
let baseName = StorageUtilities_js_1.default.getBaseFromName(fileName);
if (baseName.endsWith(".mcr")) {
baseName = baseName.substring(0, baseName.length - 4);
}
if (baseName.endsWith("addon")) {
suite = IProjectInfoData_js_1.ProjectInfoSuite.cooperativeAddOn;
baseName = baseName.substring(0, baseName.length - 6);
}
if (baseName.endsWith("currentplatform")) {
suite = IProjectInfoData_js_1.ProjectInfoSuite.currentPlatformVersions;
baseName = baseName.substring(0, baseName.length - 16);
}
let title = StorageUtilities_js_1.default.getBaseFromName(fileName);
let firstDash = title.indexOf("-");
let lastDash = title.lastIndexOf("-");
if (firstDash > 0 && lastDash > firstDash + 1) {
title = title.substring(firstDash + 1, lastDash);
}
if (projectsLoaded > 0 && projectsLoaded % 500 === 0) {
console.warn("Loaded " + projectsLoaded + " reports, @ " + title);
}
pis.mergeFeatureSetsAndFieldsTo(allFeatureSets, allFields);
// clear out icons since the aggregation won't need them, and it should save memory.
if (jsonO.info && jsonO.info["defaultIcon"]) {
jsonO.info["defaultIcon"] = undefined;
}
projectList.push({
projectContainerName: baseName,
projectPath: file.parentFolder.storageRelativePath,
projectName: baseName,
projectTitle: title,
infoSetData: jsonO,
suite: suite,
});
projectsLoaded++;
}
}
}
if (projectList.length > 0) {
await saveAggregatedReports(projectList);
}
else {
Log_js_1.default.message("Did not find any report JSON files.");
}
}
async function saveAggregatedReports(projectList) {
let outputStorage;
const csvHeader = ProjectInfoSet_js_1.default.CommonCsvHeader;
let sampleProjectInfoSets = {};
const featureSetsByName = {};
const fieldsByName = {};
const issueLines = {};
const summaryLines = {};
const mciFileList = { files: [], folders: [] };
const megaContentIndex = new ContentIndex_js_1.default();
const measures = {};
const dataMeasures = {};
if (options.outputFolder) {
outputStorage = new NodeStorage_js_1.default(options.outputFolder, "");
}
let projectsConsidered = 0;
for (const projectSet of projectList) {
let suiteName = "all";
if (projectSet.suite !== undefined) {
suiteName = ProjectInfoSet_js_1.default.getSuiteString(projectSet.suite);
}
const pisData = projectSet.infoSetData;
const contentIndex = new ContentIndex_js_1.default();
if (pisData.index) {
contentIndex.loadFromData(pisData.index);
}
const pis = new ProjectInfoSet_js_1.default(undefined, undefined, undefined, pisData.info, pisData.items, contentIndex);
const projectBaseName = StorageUtilities_js_1.default.removeContainerExtension(projectSet.projectContainerName);
if (pis.contentIndex) {
megaContentIndex.mergeFrom(pis.contentIndex, projectBaseName);
}
if (projectSet.projectName) {
mciFileList.files.push(projectBaseName.toLowerCase() + ".mci.json");
}
if (projectsConsidered > 0 && projectsConsidered % 500 === 0) {
console.warn("Processed " + projectsConsidered + " reports, @ " + projectBaseName);
}
if (featureSetsByName[suiteName] === undefined) {
featureSetsByName[suiteName] = {};
}
const featureSets = featureSetsByName[suiteName];
if (fieldsByName[suiteName] === undefined) {
fieldsByName[suiteName] = {};
}
const fields = fieldsByName[suiteName];
pis.mergeFeatureSetsAndFieldsTo(featureSets, fields);
projectsConsidered++;
}
for (const setName in featureSetsByName) {
const featureSets = featureSetsByName[setName];
if (featureSets) {
for (const featureSetName in featureSets) {
const featureSet = featureSets[featureSetName];
if (featureSet) {
measures[featureSetName] = {
name: featureSetName,
items: {},
};
}
}
}
}
for (const projectSet of projectList) {
let suiteName = "all";
if (projectSet.suite !== undefined) {
suiteName = ProjectInfoSet_js_1.default.getSuiteString(projectSet.suite);
}
const pisData = projectSet.infoSetData;
const projectBaseName = StorageUtilities_js_1.default.removeContainerExtension(projectSet.projectContainerName);
const pis = new ProjectInfoSet_js_1.default(undefined, undefined, undefined, pisData.info, pisData.items);
if (featureSetsByName[suiteName] === undefined) {
featureSetsByName[suiteName] = {};
}
const featureSets = featureSetsByName[suiteName];
if (fieldsByName[suiteName] === undefined) {
fieldsByName[suiteName] = {};
}
const fields = fieldsByName[suiteName];
pis.mergeFeatureSetsAndFieldsTo(featureSets, fields);
sampleProjectInfoSets[suiteName] = pis;
if (projectSet.suite === undefined || projectSet.suite === IProjectInfoData_js_1.ProjectInfoSuite.default) {
if (projectSet.infoSetData.info) {
for (const memberName in projectSet.infoSetData.info) {
if (ProjectInfoSet_js_1.default.isAggregableFieldName(memberName)) {
let data = dataMeasures[memberName];
if (data === undefined) {
data = { name: memberName, items: {} };
dataMeasures[memberName] = data;
}
if (projectSet.infoSetData.info[memberName] !== undefined) {
data.items[projectBaseName] = projectSet.infoSetData.info[memberName];
}
}
}
}
if (projectSet.infoSetData.info && projectSet.infoSetData.info.featureSets) {
for (const featureSetName in featureSets) {
const featureSet = projectSet.infoSetData.info.featureSets[featureSetName];
if (featureSet) {
measures[featureSetName].items[projectBaseName] = featureSet;
}
}
}
}
if (issueLines[suiteName] === undefined) {
issueLines[suiteName] = [];
}
if (issueLines[suiteName].length <= MAX_LINES_PER_CSV_FILE) {
const pisLines = pis.getItemCsvLines();
for (let j = 0; j < pisLines.length; j++) {
issueLines[suiteName].push('"' + projectBaseName + '",' + pisLines[j]);
}
}
if (summaryLines[suiteName] === undefined) {
summaryLines[suiteName] = [];
}
if (outputStorage) {
summaryLines[suiteName].push(pis.getSummaryCsvLine(projectBaseName, projectSet.projectTitle, featureSets));
}
}
if (outputStorage) {
for (const issueLinesName in issueLines) {
if (sampleProjectInfoSets[issueLinesName]) {
const issueLinesSet = issueLines[issueLinesName];
if (issueLinesSet.length < MAX_LINES_PER_CSV_FILE) {
let allCsvFile = outputStorage.rootFolder.ensureFile(issueLinesName + ".csv");
let allCsvContent = "Project," + csvHeader + "\r\n" + issueLinesSet.join("\n");
allCsvFile.setContent(allCsvContent);
await allCsvFile.saveContent();
}
}
}
for (const summaryLinesName in summaryLines) {
if (featureSetsByName[summaryLinesName] === undefined) {
featureSetsByName[summaryLinesName] = {};
}
if (sampleProjectInfoSets[summaryLinesName]) {
const featureSets = featureSetsByName[summaryLinesName];
const summaryLinesSet = summaryLines[summaryLinesName];
const projectsCsvFile = outputStorage.rootFolder.ensureFile(summaryLinesName + "projects.csv");
let projectsCsvContent = ProjectInfoSe