UNPKG

mavensmate

Version:

Core APIs that drive MavensMate IDEs for Salesforce1/Force.com

603 lines (558 loc) 18.3 kB
/** * @file Represents an element of Salesforce metadata. * @author Joseph Ferraro <@joeferraro> */ 'use strict'; var Promise = require('bluebird'); var _ = require('lodash'); var fs = require('fs-extra'); var path = require('path'); var MetadataHelper = require('./metadata').MetadataHelper; var util = require('./util'); var config = require('../config'); var request = require('request'); var swig = require('swig'); var logger = require('winston'); var TemplateService = require('./services/template'); var types = { TOP_LEVEL_METADATA_DIRECTORY: 'TOP_LEVEL_METADATA_DIRECTORY', TOP_LEVEL_METADATA_FILE: 'TOP_LEVEL_METADATA_FILE', METADATA_FOLDER: 'METADATA_FOLDER', METADATA_FOLDER_ITEM: 'METADATA_FOLDER_ITEM', LIGHTNING_BUNDLE: 'LIGHTNING_BUNDLE', LIGHTNING_BUNDLE_ITEM: 'LIGHTNING_BUNDLE_ITEM' }; Object.freeze(types); var lightningTypes = { STYLE: 'STYLE', APPLICATION: 'APPLICATION', DOCUMENTATION: 'DOCUMENTATION', COMPONENT: 'COMPONENT', EVENT: 'EVENT', INTERFACE: 'INTERFACE', CONTROLLER: 'CONTROLLER', HELPER: 'HELPER', RENDERER: 'RENDERER', TOKENS: 'TOKENS', DESIGN: 'DESIGN', SVG: 'SVG' }; Object.freeze(lightningTypes); var MavensMateFile = function(opts) { opts = opts || {}; this.path = opts.path; this.project = opts.project; this.metadataHelper = this.project ? new MetadataHelper({ sfdcClient : this.project.sfdcClient }) : new MetadataHelper(); if (this.path) { this.path = path.normalize(this.path); this.type = this.metadataHelper.getTypeByPath(this.path); this.basename = path.basename(this.path); this.extension = path.extname(this.path).replace(/./, ''); if (this.basename.match(/./g)||[].length === 1) { this.name = this.basename.split('.')[0]; } else { this.name = path.basename(this.basename, '.' + this.extension); } this.folderName = path.basename(path.dirname(this.path)); } }; MavensMateFile.prototype._template = null; MavensMateFile.prototype._basename = null; MavensMateFile.prototype._name = null; MavensMateFile.prototype._folderName = null; MavensMateFile.prototype._extension = null; MavensMateFile.prototype._apexTriggerObjectName = null; /** * whether the path represents a directory */ Object.defineProperty(MavensMateFile.prototype, 'isDirectory', { get: function() { if (this.type.xmlName === 'Document') { return path.extname(this.path) === ''; //TODO: some documents may not have an extension! } else { return path.extname(this.path) === ''; } } }); /** * basename of path */ Object.defineProperty(MavensMateFile.prototype, 'basename', { get: function() { return this._basename; }, set: function(value) { this._basename = value; } }); /** * object name of apex trigger (if applicable) */ Object.defineProperty(MavensMateFile.prototype, 'apexTriggerObjectName', { get: function() { return this._apexTriggerObjectName; }, set: function(value) { this._apexTriggerObjectName = value; } }); /** * basename of path without extension */ Object.defineProperty(MavensMateFile.prototype, 'name', { get: function() { return this._name; }, set: function(value) { this._name = value; } }); /** * basename of path without extension */ Object.defineProperty(MavensMateFile.prototype, 'folderName', { get: function() { return this._folderName; }, set: function(value) { this._folderName = value; } }); /** * basename of path without extension */ Object.defineProperty(MavensMateFile.prototype, 'extension', { get: function() { return this._extension; }, set: function(value) { this._extension = value; } }); /** * name when referenced via package.xml */ Object.defineProperty(MavensMateFile.prototype, 'packageName', { get: function() { if (this.classification === types.METADATA_FOLDER_ITEM) { return this.folderName + '/' + this.name; } else { return this.name; } } }); Object.defineProperty(MavensMateFile.prototype, 'isToolingType', { get: function() { var supportedExtensions = ['cls', 'trigger', 'page', 'component']; return supportedExtensions.indexOf(this.extension) >= 0; } }); Object.defineProperty(MavensMateFile.prototype, 'isLightningType', { get: function() { return this.type.xmlName === 'AuraDefinitionBundle'; } }); /** * Returns base name of lightning component (e.g. fooRenderer -> foo) * @return {String} */ Object.defineProperty(MavensMateFile.prototype, 'lightningBaseName', { get: function() { var lbn = this.name; if (util.endsWith(lbn, 'Controller')) { lbn = lbn.replace(/Controller/, ''); } else if (util.endsWith(lbn, 'Helper')) { lbn = lbn.replace(/Helper/, ''); } if (util.endsWith(lbn, 'Renderer')) { lbn = lbn.replace(/Renderer/, ''); } return lbn; }, }); Object.defineProperty(MavensMateFile.prototype, 'lightningType', { get: function() { if (this.extension === 'css') { return 'STYLE'; } else if (this.extension === 'app') { return 'APPLICATION'; } else if (this.extension === 'auradoc') { return 'DOCUMENTATION'; } else if (this.extension === 'cmp') { return 'COMPONENT'; } else if (this.extension === 'evt') { return 'EVENT'; } else if (this.extension === 'intf') { return 'INTERFACE'; } else if (this.extension === 'tokens') { return 'TOKENS'; } else if (this.extension === 'design') { return 'DESIGN'; } else if (this.extension === 'svg') { return 'SVG'; } else if (this.extension === 'js') { if (util.endsWith(this.name, 'Controller')) { return 'CONTROLLER'; } else if (util.endsWith(this.name, 'Helper')) { return 'HELPER'; } else if (util.endsWith(this.name, 'Renderer')) { return 'RENDERER'; } } } }); /** * classification of the path */ Object.defineProperty(MavensMateFile.prototype, 'classification', { get: function() { if (!this.type) throw new Error('Unrecognized metadata type: '+this.path); if (this.type.inFolder) { var inFolderTypeDirectoryNames = this.metadataHelper.inFolderDirectoryNames; if (this.isDirectory) { if (inFolderTypeDirectoryNames.indexOf(path.basename(path.dirname(this.path))) >= 0) { return types.TOP_LEVEL_METADATA_DIRECTORY; } else { return types.METADATA_FOLDER; } } else { return types.METADATA_FOLDER_ITEM; } } else if (this.type.xmlName === 'AuraDefinitionBundle') { if (this.isDirectory) { return types.LIGHTNING_BUNDLE; } else { return types.LIGHTNING_BUNDLE_ITEM; } } else { if (this.isDirectory) { return types.TOP_LEVEL_METADATA_DIRECTORY; } else { return types.TOP_LEVEL_METADATA_FILE; } } } }); /** * Local file body (source code, conents, etc.) */ Object.defineProperty(MavensMateFile.prototype, 'body', { get: function() { if (this.isDirectory) { throw new Error('Can not get body of directory'); } return util.getFileBody(this.path); } }); /** * Returns whether this path type requires a corresponding meta file * @return {String} */ Object.defineProperty(MavensMateFile.prototype, 'hasMetaFile', { get: function() { return this.type.metaFile === true; } }); /** * Whether the instance exists on the disk (or virtual disk (future)) * @return {Boolean} */ Object.defineProperty(MavensMateFile.prototype, 'existsOnFileSystem', { get: function() { return this.path ? fs.existsSync(this.path) : false; } }); /** * Whether this is a -meta.xml file * @return {Boolean} */ Object.defineProperty(MavensMateFile.prototype, 'isMetaFile', { get: function() { return util.endsWith(this.path, '-meta.xml'); } }); /** * Id of the file on the server */ Object.defineProperty(MavensMateFile.prototype, 'id', { get: function() { try { if (this.isDirectory) { throw new Error('Cannot get server id for directory.'); } else { // determine id (useful for lightning/apex/vf types bc tooling api is preferential to ids) if (this.isLightningType && this.project) { var lightningIndex = this.project.getLightningIndexSync(); return _.find(lightningIndex, { AuraDefinitionBundle : { DeveloperName: this.lightningBaseName }, DefType: this.lightningType }).Id; } else if (this.project) { return this.project.getLocalStore()[this.basename].id; } } } catch(e){ logger.debug('Could not determine metadata id: '+e.message); } } }); Object.defineProperty(MavensMateFile.prototype, 'localStoreEntry', { get: function() { try { if (this.isDirectory || this.isLightningType) { throw new Error('Cannot get local store entry for directories or lightning types currently.'); } else { return this.project.getLocalStore()[this.basename]; } } catch(e){ logger.debug('Could not determine local store entry: '+e.message); } } }); Object.defineProperty(MavensMateFile.prototype, 'serverCopy', { get: function() { var self = this; return new Promise(function(resolve, reject) { try { if (self.isDirectory || self.isLightningType) { throw new Error('Cannot get server contents for directories or lightning types currently.'); } else { if (!self.project) { throw new Error('Cannot get server contents without a valid project instance.'); } var supportedTypeXmlNames = ['ApexClass','ApexPage','ApexComponent','ApexTrigger']; if (supportedTypeXmlNames.indexOf(self.type.xmlName) === -1) { throw new Error('serverContents only supports Apex types.'); } var bodyField = (self.type.xmlName === 'ApexPage' || self.type.xmlName === 'ApexComponent') ? 'Markup' : 'Body'; var soql = 'Select LastModifiedById, LastModifiedDate, LastModifiedBy.Name, '+bodyField+' From '+self.type.xmlName+' Where Name = \''+self.name+'\''; self.project.sfdcClient.conn.query(soql, function(err, result) { if (err) { logger.error('SOQL for server copy failed', err.message); return reject(err); } else if (result.records.length === 0) { var err = new Error(self.name+' ('+self.type.xmlName+') was not found on the server.'); logger.error(err.message, soql); return reject(err); } result.records[0].Body = result.records[0][bodyField]; resolve(result.records[0]); }); } } catch(e) { logger.error('Could not determine local store entry: '+e.message); reject(e); } }); } }); /** * local files in this directory */ Object.defineProperty(MavensMateFile.prototype, 'localMembers', { get: function() { if (!this.isDirectory) { throw new Error('localMembers property is only supported for directory types'); } var self = this; var contents = []; var directoryFiles = fs.readdirSync(self.path); _.each(directoryFiles, function(f) { contents.push(new MavensMateFile({ path: path.join(self.path, f), project: self.project })); if (!path.extname(f)) { var subDirectoryFiles = fs.readdirSync(path.join(self.path, f)); _.each(subDirectoryFiles, function(sf) { contents.push(new MavensMateFile({ path: path.join(self.path, f, sf), project: self.project })); }); } }); return contents; } }); /** * Returns base name of the folder (e.g. path/to/src/documents/foldername/foo.txt -> foldername) * Currently, salesforce does not support folders nested deeper than 1 level * @return {String} */ Object.defineProperty(MavensMateFile.prototype, 'folderBaseName', { get: function() { var folderPath = path.dirname(this.path); return path.basename(folderPath); }, }); /** * template * @return {Object} */ Object.defineProperty(MavensMateFile.prototype, 'template', { get: function() { return this._template; }, set: function(value) { this._template = value; } }); /** * Returns base name of the package.xml subscription * @return {String} */ Object.defineProperty(MavensMateFile.prototype, 'subscriptionName', { get: function() { if (this.type.inFolder) { if (this.type.xmlName === 'Document') { return this.folderBaseName + '/' + this.name + '.' + this.extension; } else { return this.folderBaseName + '/' + this.name; } } else { return this.name; } }, }); MavensMateFile.prototype.setTypeByXmlName = function(xmlName) { this.type = this.metadataHelper.getTypeByXmlName(xmlName); }; MavensMateFile.prototype.setAbstractPath = function() { this.path = this.type.directoryName + '/' + this.name + '.' + this.type.suffix; this.extension = this.type.suffix; this.basename = path.basename(this.path); this.name = this.basename.split('.')[0]; this.folderName = path.basename(path.dirname(this.path)); }; /** * Returns the MavensMate-Templates template body based on this.template * @return {Promise} - resolves with {String} template body */ MavensMateFile.prototype._getTemplateBody = function() { var self = this; return new Promise(function(resolve, reject) { try { var templateService = new TemplateService(); resolve(templateService.getTemplateBody(self.type.xmlName, self.template.file_name)); } catch(e) { logger.error('Could not get template body', e); reject(e); } }); }; MavensMateFile.prototype.mergeTemplate = function() { var self = this; return new Promise(function(resolve, reject) { var apiName = self.name; self._getTemplateBody() .then(function(templateBody) { resolve(swig.render(templateBody, { locals: self.templateValues })); }) .catch(function(err) { reject(err); }) .done(); }); }; /** * Renders template and writes to appropriate destination * @param {String} deployPath * @return {Promise} */ MavensMateFile.prototype.renderAndWriteToDisk = function(destination) { var self = this; return new Promise(function(resolve, reject) { var apiName = self.name; self._getTemplateBody() .then(function(templateBody) { var filePath = path.join(destination, self.type.directoryName, [apiName,self.type.suffix].join('.')); var fileBody = swig.render(templateBody, { locals: self.templateValues }); fs.outputFileSync(filePath, fileBody); if (self.hasMetaFile) { var metaFilePath = path.join(destination, self.type.directoryName, [apiName,self.type.suffix+'-meta.xml'].join('.')); var metaFileBody = swig.renderFile(path.join(__dirname, 'templates', 'meta.xml'), { metadata: self, apiVersion: config.get('mm_api_version') }); fs.outputFileSync(metaFilePath, metaFileBody); } resolve(); }) .catch(function(err) { reject(new Error('Could not write metadata file based on template: '+err)); }) .done(); }); }; MavensMateFile.prototype.writeToDiskSync = function(body) { body = body || ''; if (this.isDirectory && this.path) { fs.ensureDirSync(this.path); } else if (!this.isDirectory && this.path) { fs.outputFileSync(this.path, body); } }; MavensMateFile.prototype.deleteLocally = function() { if (this.hasMetaFile && fs.existsSync(this.path+'-meta.xml')) { fs.removeSync(this.path+'-meta.xml'); } if (this.existsOnFileSystem) { fs.removeSync(this.path); } }; module.exports.createFileInstances = function(paths, project) { var files = []; _.each(paths, function(p) { files.push(new MavensMateFile({ path: p, project: project })); }); return files; }; module.exports.getLightningBundleItemFiles = function(files) { return _.filter(files, function(f) { return f.classification === types.LIGHTNING_BUNDLE_ITEM; }); }; module.exports.getToolingFiles = function(files, exludeToolingMetadata) { return _.filter(files, function(f) { return !exludeToolingMetadata && f.isToolingType; }); }; module.exports.getMetadataApiFiles = function(files, exludeToolingMetadata) { return _.filter(files, function(f) { if (f.isMetaFile) { return true; } else if (f.classification === types.LIGHTNING_BUNDLE_ITEM) { return false; } else if (exludeToolingMetadata && f.isToolingType) { return false; } return true; }); }; module.exports.createPackageSubscription = function(files, projectPackageXml, exludeToolingMetadata) { var subscription = {}; var projectSubscription = {}; if (projectPackageXml) { projectSubscription = projectPackageXml.subscription; } _.each(files, function(f) { if (f.isToolingType && exludeToolingMetadata) { return; // (continue) } if (f.classification === types.TOP_LEVEL_METADATA_DIRECTORY && projectSubscription) { // classes, ApexClass subscription[f.type.xmlName] = projectSubscription[f.type.xmlName]; } else if (f.classification === types.TOP_LEVEL_METADATA_FILE) { if (subscription[f.type.xmlName]) { if (subscription[f.type.xmlName] !== '*') { subscription[f.type.xmlName].push(f.packageName); } } else { subscription[f.type.xmlName] = [ f.packageName ]; } } else if (f.classification === types.METADATA_FOLDER || f.classification === types.METADATA_FOLDER_ITEM) { if (subscription[f.type.xmlName]) { subscription[f.type.xmlName].push(f.packageName); } else { subscription[f.type.xmlName] = [ f.packageName ]; } } else if (f.classification === types.LIGHTNING_BUNDLE) { if (subscription[f.type.xmlName]) { subscription[f.type.xmlName].push(f.packageName); } else { subscription[f.type.xmlName] = [ f.packageName ]; } } }); return subscription; }; module.exports.types = types; module.exports.MavensMateFile = MavensMateFile;