import { createHash } from "crypto"; import { join } from "path"; import * as tempDir from "temp-dir"; import { mta } from "../index"; import { runMta } from "./process-utils"; import { pathExists } from "fs-extra"; const tempFileName = "temp.mta.yaml"; const mtaFileName = "mta.yaml"; export const MTA_NOT_FOUND_MESSAGE = "Multi-target application not found"; function parseOutput(output: string) { try { output = output.trim().replace(/^.{0}$/, "{}"); return JSON.parse(output); } catch (e) { /* istanbul ignore next */ throw new Error(`Not a valid JSON output: ${output}\n${e.message}`); } } export class Mta { public readonly mtaDirPath: string; /** When mtaTempFilePath is undefined we work on mtaFilePath directly */ private readonly mtaTempFilePath: string | undefined; private readonly mtaFilePath: string; private readonly mtaExtFilePaths: string[]; private hashcode: string | undefined; public constructor( mtaDirPath: string, useTempFile = true, mtaExtFilePaths: string[] = [] ) { this.mtaDirPath = mtaDirPath; this.mtaExtFilePaths = mtaExtFilePaths; if (useTempFile) { const hash = createHash("md5").update(this.mtaDirPath).digest("hex"); this.mtaTempFilePath = join(tempDir, hash + "." + tempFileName); } this.mtaFilePath = join(this.mtaDirPath, mtaFileName); } /** * Creates a new temporary mta.yaml file (or mta.yaml file, if useTempFile is false) with the descriptor. */ public async create(descriptor: mta.MtaDescriptor): Promise { await this.clean(); const output = await runMta([ "create", "-p", this.mtaTempFilePath ?? this.mtaFilePath, "-d", JSON.stringify(descriptor), ]); this.hashcode = parseOutput(output).hashcode; } /** * Gets the MTA file path. If the MTA file path doesn't exist, an error message is thrown. */ public async getMtaFilePath(includeTemp = true): Promise { if ( this.mtaTempFilePath !== undefined && includeTemp && (await pathExists(this.mtaTempFilePath)) ) { return this.mtaTempFilePath; } else if (await pathExists(this.mtaFilePath)) { return this.mtaFilePath; } throw new Error(MTA_NOT_FOUND_MESSAGE); } /** * Gets the MTA ID. */ public async getMtaID(): Promise { return await this.runOnExistingFile(["get", "id"]); } /** * Adds a new module. */ public async addModule(module: mta.Module, force = false): Promise { const args = ["add", "module", "-d", JSON.stringify(module)]; if (force) { args.push("-f"); } await this.runOnTempFile(args); } /** * Adds a new resource. */ public async addResource( resource: mta.Resource, force = false ): Promise { const args = ["add", "resource", "-d", JSON.stringify(resource)]; if (force) { args.push("-f"); } await this.runOnTempFile(args); } private addMtaExtFlagsToCommandArgs(command: string[]): string[] { const flagsAndMtaExtFilePaths: string[] = []; this.mtaExtFilePaths.forEach((mtaExtFilePath) => flagsAndMtaExtFilePaths.push("-x", mtaExtFilePath) ); return command.concat(flagsAndMtaExtFilePaths); } /** * validate the mta.yaml and the defined the mta ext files. */ public async validate(): Promise> { const getModulesCommand = this.addMtaExtFlagsToCommandArgs(["validate"]); return await this.runOnExistingFile(getModulesCommand); } /** * Gets all modules. */ public async getModules(): Promise { const getModulesCommand = this.addMtaExtFlagsToCommandArgs([ "get", "modules", ]); return await this.runOnExistingFile(getModulesCommand); } /** * Gets all resources */ public async getResources(): Promise { const getResourceCommand = this.addMtaExtFlagsToCommandArgs([ "get", "resources", ]); return await this.runOnExistingFile(getResourceCommand); } /** * Gets parameters */ public async getParameters(): Promise { const getParametersCommand = this.addMtaExtFlagsToCommandArgs([ "get", "parameters", ]); return await this.runOnExistingFile(getParametersCommand); } /** * Gets build parameters */ public async getBuildParameters(): Promise { const getBuildParametersCommand = this.addMtaExtFlagsToCommandArgs([ "get", "buildParameters", ]); return await this.runOnExistingFile(getBuildParametersCommand); } public async getResourceConfig( resourceName: string ): Promise> { let getResourceConfigCommand = [ "get", "resource-config", "-r", resourceName, "-w", this.mtaDirPath, ]; getResourceConfigCommand = this.addMtaExtFlagsToCommandArgs( getResourceConfigCommand ); return await this.runOnExistingFile(getResourceConfigCommand); } /** * Updates an existing module. */ public async updateModule(module: mta.Module): Promise { await this.runOnTempFile([ "update", "module", "-d", JSON.stringify(module), ]); } /** * Updates an existing resource. */ public async updateResource(resource: mta.Resource): Promise { await this.runOnTempFile([ "update", "resource", "-d", JSON.stringify(resource), ]); } /** * Updates parameters * Updates the existing parameters or adds new parameters if they don't exist. */ public async updateParameters(parameters: mta.Parameters): Promise { await this.runOnTempFile([ "update", "parameters", "-d", JSON.stringify(parameters), ]); } /** * Updates the build parameters. * Updates the existing build parameters or adds new build parameters if they don't exist. */ public async updateBuildParameters( buildParameters: mta.ProjectBuildParameters, force = false ): Promise { const args = [ "update", "buildParameters", "-d", JSON.stringify(buildParameters), ]; if (force) { args.push("-f"); } await this.runOnTempFile(args); } /** * Checks if the name given exists in the "mta.yaml" file. * @param name */ public async doesNameExist(name: string): Promise { return await this.runOnExistingFile(["exist", "-n", name]); } /** * Saves the "mta.yaml" file: * Copies the "temp.mta.yaml" file content to the "mta.yaml" file. * Deletes the "temp.mta.yaml" file. */ public async save(): Promise { if ( this.mtaTempFilePath !== undefined && (await pathExists(this.mtaTempFilePath)) ) { const output = await runMta([ "copy", "-s", this.mtaTempFilePath, "-t", this.mtaFilePath, ]); await runMta(["deleteFile", "-p", this.mtaTempFilePath]); this.hashcode = parseOutput(output).hashcode; } } /** * Deletes the temporary mta file. */ public async clean(): Promise { if ( this.mtaTempFilePath !== undefined && (await pathExists(this.mtaTempFilePath)) ) { await runMta(["deleteFile", "-p", this.mtaTempFilePath]); } } /** * Resolve mta module. */ public async resolveModuleProperties( moduleName: string, envFile?: string ): Promise<{ properties: Record; messages: string[] }> { let args = [ "resolve", "-m", moduleName, "-o", "json", "-w", this.mtaDirPath, ]; if (envFile !== undefined) { args = args.concat(["-e", envFile]); } args = this.addMtaExtFlagsToCommandArgs(args); return this.runOnExistingFile(args); } /** * Runs the MTA command with the sent arguments and the additional argument `-p `. * Before running the MTA command, the function makes sure that the "temp.mta.yaml" exists * according to the following logic: * If the "temp.mta.yaml" file exists, runs the MTA command on it. * If the "temp.mta.yaml" file doesn't exist, checks if the "mta.yaml" file exists; * if not, displays an error message. * If the "mta.yaml" file exists, copies the "mta.yaml" file content to the "temp.mta.yaml" file * and then runs the MTA command on it. * * @param args - the arguments to send to the MTA executable. */ private async runOnTempFile(args: string[]): Promise { let filePath; // When this.mtaTempFilePath is undefined we work on this.mtaFilePath directly if (this.mtaTempFilePath === undefined) { filePath = this.mtaFilePath; if (!(await pathExists(this.mtaFilePath))) { throw new Error(MTA_NOT_FOUND_MESSAGE); } } else { filePath = this.mtaTempFilePath; // Create the temp file if it doesn't exist if (!(await pathExists(this.mtaTempFilePath))) { if (!(await pathExists(this.mtaFilePath))) { throw new Error(MTA_NOT_FOUND_MESSAGE); } const output = await runMta([ "copy", "-s", this.mtaFilePath, "-t", this.mtaTempFilePath, ]); this.hashcode = parseOutput(output).hashcode; } } args = args.concat(["-p", filePath, "-c", this.hashcode ?? "0"]); const output = await runMta(args); const outputObj = parseOutput(output); this.hashcode = outputObj.hashcode; return outputObj.result; } private async runOnExistingFile(args: string[]): Promise { args = args.concat(["-p", await this.getMtaFilePath()]); const output = await runMta(args); const outputObj = parseOutput(output); this.hashcode = outputObj.hashcode; return outputObj.result; } }