salesforce-alm
Version:
This package contains tools, and APIs, for an improved salesforce.com developer experience.
204 lines (202 loc) • 9.17 kB
JavaScript
;
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
// Node
const path = require("path");
const util = require("util");
const core_1 = require("@salesforce/core");
// Thirdparty
const optional = require("optional-js");
const BBPromise = require("bluebird");
const _ = require("lodash");
// Local
const logApi = require("../core/logApi");
const almError = require("../core/almError");
const MetadataRegistry = require("./metadataRegistry");
const metadataTypeFactory_1 = require("./metadataTypeFactory");
const Builder = require('fast-xml-parser').j2xParser;
function createOutputXmlManifestFile(fileName, packageManifestJson) {
const builder = new Builder({
format: true,
attrNodeName: '$',
});
const xmlDeclaration = '<?xml version="1.0" encoding="UTF-8"?>\n';
const xml = xmlDeclaration.concat(builder.parse(packageManifestJson)).trim();
return core_1.fs.writeFile(fileName, xml).then(() => ({
file: fileName,
manifest: packageManifestJson,
}));
}
function generateMetadataManifestJson(packageName, typesAsKeyValuePairsArray, apiVersion) {
const MdapiPackage = require('./mdapiPackage'); // eslint-disable-line global-require
const mdPackage = new MdapiPackage();
mdPackage.setVersion(apiVersion);
if (!util.isNullOrUndefined(packageName)) {
mdPackage.setPackageName(packageName);
}
typesAsKeyValuePairsArray.forEach((typeNamePair) => {
mdPackage.addMember(typeNamePair.name, typeNamePair.type);
});
return mdPackage.getPackage();
}
function processMetadataFile(dir, file, childLogger, metadataRegistry) {
const filePath = path.resolve(dir, file);
const metadataType = metadataTypeFactory_1.MetadataTypeFactory.getMetadataTypeFromSourcePath(filePath, metadataRegistry);
let fileInfo = null;
if (metadataType) {
const fullName = metadataType.getAggregateFullNameFromFilePath(filePath);
if (!util.isNullOrUndefined(fullName)) {
fileInfo = { type: metadataType.getMetadataName(), name: fullName };
}
}
if (fileInfo === null) {
childLogger.info(`WARNING: Error parsing metadata file. Ignoring - ${filePath}`);
}
return BBPromise.resolve(fileInfo);
}
function readMetadataDirectoryContent(dir) {
return core_1.fs
.readdir(dir)
.then((files) => BBPromise.map(files, (file) => core_1.fs.stat(path.resolve(dir, file)).then((stats) => ({
name: file,
isDirectory: stats.isDirectory(),
}))))
.then((fileInfoArray) => {
const dirContent = { metadataFiles: [], dirs: [] };
fileInfoArray.forEach((fileInfo) => {
if (fileInfo.isDirectory) {
dirContent.dirs.push(fileInfo.name);
}
else if (fileInfo.name.endsWith(MetadataRegistry.getMetadataFileExt())) {
dirContent.metadataFiles.push(fileInfo.name);
}
});
return dirContent;
});
}
function processMetadataDirectory(dir, childLogger, metadataRegistry, manifestcreate) {
return readMetadataDirectoryContent(dir)
.then((entriesToBeProcessed) => BBPromise.map(entriesToBeProcessed.metadataFiles, (file) => processMetadataFile(dir, file, childLogger, metadataRegistry)).then((resultFromFiles) => BBPromise.all([
resultFromFiles,
BBPromise.map(entriesToBeProcessed.dirs, (childDir) => processMetadataDirectory(path.resolve(dir, childDir), childLogger, metadataRegistry, manifestcreate)),
])))
.then((resultAsKeyValuePairs) => {
// Flatten result from previous step which contains multi-level arrays due
// to the way promise combines results from files in current directory and files
// in child directory.
const elementsToProcess = [];
const flattenedResultObj = [];
elementsToProcess.push(resultAsKeyValuePairs);
while (elementsToProcess.length > 0) {
const nextElement = elementsToProcess[0];
if (Array.isArray(nextElement)) {
if (nextElement.length > 0) {
for (const element of nextElement) {
if (element) {
elementsToProcess.push(element);
}
}
}
}
else if (nextElement) {
flattenedResultObj.push(nextElement);
}
elementsToProcess.shift();
}
return flattenedResultObj;
});
}
/**
* API object to create manifest file.
*
* @constructor
*/
const manifestCreate = function (org, beforeManifestGenerationHook) {
this.org = org;
this.config = this.org.config;
this.apiVersion = this.config.getAppConfig().sourceApiVersion;
this.logger = logApi.child('manifest-create');
this._fsStat = core_1.fs.stat;
this._fsMkdir = core_1.fs.mkdirp;
this.beforeManifestGenerationHook = beforeManifestGenerationHook;
};
manifestCreate.prototype.execute = function execute(context) {
const projectDirectory = this.config.getProjectPath();
const appConfig = this.config.getAppConfig();
// use defaultArtifact which is root dir of source (if set, prepend project dir)
const defaultSourceDirectory = !util.isNullOrUndefined(appConfig.defaultPackagePath)
? path.join(projectDirectory, appConfig.defaultPackagePath)
: projectDirectory;
const rootDirectory = optional.ofNullable(context.sourcedir).orElse(defaultSourceDirectory);
this.outputDirectory = optional.ofNullable(context.outputdir).orElse(projectDirectory);
const outputFile = path.resolve(this.outputDirectory, 'package.xml');
const apiVersion = this.apiVersion;
return this._validateDirectory(rootDirectory, almError('InvalidArgumentDirectoryPath', ['sourcedir', rootDirectory]))
.then(() => this._createDirIfNotExists(this.outputDirectory))
.then(() => this._validateDirectory(this.outputDirectory, almError('InvalidArgumentDirectoryPath', ['outputdir', this.outputDirectory])))
.then(() => {
this.metadataRegistry = new MetadataRegistry();
return processMetadataDirectory(rootDirectory, this.logger, this.metadataRegistry);
})
.then((resultAsKeyValuePairs) => {
if (this.beforeManifestGenerationHook) {
resultAsKeyValuePairs = this.beforeManifestGenerationHook(resultAsKeyValuePairs);
}
if (context.exclusions) {
resultAsKeyValuePairs = resultAsKeyValuePairs.filter((element) => _.isNil(context.exclusions.find((exclusion) => exclusion.type === element.type && exclusion.name === element.name)));
}
const packageManifestJson = generateMetadataManifestJson(context.packageName, resultAsKeyValuePairs, apiVersion);
return createOutputXmlManifestFile(outputFile, packageManifestJson);
});
};
manifestCreate.prototype.createManifest = function (context, packageName, typeFullNamePairs) {
const outputDir = optional.ofNullable(context.outputdir).orElse(this.config.getProjectPath());
const outputFile = path.resolve(outputDir, optional.ofNullable(context.outputfile).orElse('package.xml'));
const sourceApiVersion = !util.isNullOrUndefined(context.sourceApiVersion)
? context.sourceApiVersion
: this.apiVersion;
const packageManifestJson = generateMetadataManifestJson(packageName, typeFullNamePairs, sourceApiVersion);
return createOutputXmlManifestFile(outputFile, packageManifestJson);
};
/**
* Creates an mdapi compatible package.xml manifest from an mdapiPackage
*
* @param {object} context - looking for context.outputdir; location for writing the package.xml
* @param {object} mdapiPackage - The mdapi package
*/
manifestCreate.prototype.createManifestForMdapiPackage = function (context, mdapiPackage, metadataRegistry) {
const outputFile = path.resolve(optional.ofNullable(context.outputdir).orElse(this.config.getProjectPath()), 'package.xml');
return createOutputXmlManifestFile(outputFile, mdapiPackage.getPackage(metadataRegistry));
};
manifestCreate.prototype._validateDirectory = function (dir, failWithErr) {
return this._fsStat(dir)
.then((dirStats) => {
if (!dirStats.isDirectory()) {
return BBPromise.reject(failWithErr);
}
return BBPromise.resolve();
})
.catch((err) => {
if (err.code === 'ENOENT') {
return BBPromise.reject(almError('PathDoesNotExist', dir));
}
return BBPromise.reject(err);
});
};
manifestCreate.prototype._createDirIfNotExists = function (dir) {
return (this._fsStat(dir)
// eslint-disable-next-line @typescript-eslint/no-empty-function
.then(() => { })
.catch((err) => {
if (err.code === 'ENOENT') {
return this._fsMkdir(dir);
}
return BBPromise.reject(err);
}));
};
module.exports = manifestCreate;
//# sourceMappingURL=manifestCreateApi.js.map