├── .gitignore ├── LICENSE.md ├── README.md ├── bin └── cli ├── index.js ├── lib ├── cleanUpRepo.js ├── gitDiffHandler.js ├── packageConstructor.js └── utils │ ├── fileUtils.js │ └── metadata.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | repo 2 | output 3 | node_modules 4 | .yarnclean 5 | yarn.lock 6 | npm-debug.log 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Sebastien Colladon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sfdc-git-package 2 | 3 | Create Package.xml and destructiveChangesPre.xml from git diff between two commits 4 | 5 | ## Getting Started 6 | 7 | Works in Unix like system. 8 | Windows is not tested. 9 | 10 | ### Prerequisites 11 | 12 | Git command line is required on the system where the command line is running. 13 | 14 | ### Installing 15 | 16 | ``` 17 | npm install -g sfdc-git-package 18 | ``` 19 | 20 | or 21 | 22 | ``` 23 | yarn globally add sfdc-git-package 24 | ``` 25 | 26 | ## Usage 27 | 28 | ### Command Line 29 | 30 | ``` 31 | $ sgp -h 32 | 33 | Usage: sgp [options] 34 | 35 | Create Package.xml and destructiveChangesPre.xml from git 36 | 37 | Options: 38 | 39 | -h, --help output usage information 40 | -V, --version output the version number 41 | -t, --to [sha] commit sha to where the diff is done [HEAD] 42 | -f, --from [sha] commit sha from where the diff is done [git rev-list --max-parents=0 HEAD] 43 | -o, --output [dir] package.xml specific output [./output] 44 | -a, --api-version [version] salesforce API version [37.0] 45 | -r, --repo [dir] git repository location [./repo] 46 | ``` 47 | 48 | ### Module 49 | 50 | ``` 51 | var sgp = require('sfdc-git-package'); 52 | 53 | sgp({ 54 | 'to':'', // commit sha to where the diff is done. Default : HEAD 55 | 'from':'', // commit sha from where the diff is done. Default : git rev-list --max-parents=0 HEAD 56 | 'output':'', // package.xml & destructiveChangesPre.xml specific output. Default : ./output 57 | 'apiVersion':'', // salesforce API version. Default : 39.0 58 | 'repo':'' // git repository location. Default : ./repo 59 | }, console.log); 60 | ``` 61 | 62 | 63 | ## Built With 64 | 65 | * [commander](https://github.com/tj/commander.js/) - The complete solution for node.js command-line interfaces, inspired by Ruby's commander. 66 | * [nodegit-kit](https://github.com/thisconnect/nodegit-kit) - Complementary NodeGit helpers returning native Promises, helps with git commands such as init, add, commit, status, diff. 67 | * [xmlbuilder](https://github.com/oozcitak/xmlbuilder-js) - An XML builder for node.js similar to java-xmlbuilder. 68 | 69 | ## Versioning 70 | 71 | [SemVer](http://semver.org/) is used for versioning. 72 | 73 | ## Authors 74 | 75 | * **Sebastien Colladon** - *Initial work* - [scolladon](https://github.com/scolladon) 76 | 77 | ## License 78 | 79 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 80 | -------------------------------------------------------------------------------- /bin/cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const program = require('commander'); 4 | const orchestrator = require('../index.js'); 5 | const pjson = require('../package.json'); 6 | 7 | 8 | program 9 | .description(pjson.description) 10 | .version(pjson.version) 11 | .option('-t, --to [sha]', 'commit sha to where the diff is done [HEAD]', 'HEAD') 12 | .option('-f, --from [sha]', 'commit sha from where the diff is done [git rev-list --max-parents=0 HEAD]', '') 13 | .option('-o, --output [dir]', 'package.xml & destructiveChangesPre.xml specific output [./output]', './output') 14 | .option('-a, --api-version [version]', 'salesforce API version [39.0]', '39.0') 15 | .option('-r, --repo [dir]', 'git repository location [./repo]', './repo') 16 | .parse(process.argv); 17 | 18 | orchestrator(program, console.log) 19 | .catch(function(err){ 20 | console.log(err); 21 | }); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const gitDiff = require('./lib/gitDiffHandler.js'); 2 | const pack = require('./lib/packageConstructor'); 3 | const fileUtils = require('./lib/utils/fileUtils'); 4 | 5 | module.exports = function(config, logger) { 6 | 7 | return new Promise(function (resolve, reject) { 8 | 9 | if(typeof config.to === 'undefined' || config.to === null 10 | || typeof config.from === 'undefined' || config.from === null 11 | || typeof config.apiVersion === 'undefined' || config.apiVersion === null 12 | || typeof config.output === 'undefined' || config.output === null 13 | || typeof config.repo === 'undefined' || config.repo === null) { 14 | logger('Not enough config options'); 15 | return; 16 | } 17 | 18 | const git = new gitDiff(config); 19 | const pc = new pack(config); 20 | const fu = new fileUtils(config); 21 | 22 | git.diff() 23 | .then(pc.constructPackage) 24 | .then(fu.writeAsync) 25 | .then(function(res){ 26 | if(res){ 27 | res.forEach(function(name){ 28 | logger(name + ' created'); 29 | }); 30 | } 31 | resolve(); 32 | }) 33 | .catch(function(err){reject(err)}) 34 | }); 35 | }; -------------------------------------------------------------------------------- /lib/cleanUpRepo.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | module.exports = function(config,package) { 5 | let walkSync = dir => 6 | fs.lstatSync(dir).isDirectory() 7 | ? fs.readdirSync(dir).map(f => walkSync(path.join(dir, f))) 8 | : dir 9 | 10 | const flatten = arr => arr.reduce( 11 | (a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), [] 12 | ); 13 | 14 | this.cleanUpRepo = () => { 15 | let files = walkSync(config.repo+'/src'); 16 | files = flatten(files); 17 | 18 | let flattenPackage = [] 19 | 20 | Object.keys(package).forEach(key => { 21 | package[key].forEach(el => { 22 | if(el !== 'undefined'){ 23 | flattenPackage.push(el); 24 | } 25 | }); 26 | }); 27 | 28 | files.forEach(file => { 29 | let filePath = path.parse(file); 30 | //let name = filePath.base.replace(/\.[^/.]+$/, '').replace(/\..*\-meta/,''); 31 | let name = filePath.dir + '/' + filePath.base 32 | let found = false; 33 | for(let i = flattenPackage.length - 1 ; i >= 0 ; --i) { 34 | if(name.indexOf(flattenPackage[i]) !== -1) { 35 | found = true; 36 | break; 37 | } 38 | } 39 | if(!found) { 40 | fs.unlinkSync(file); 41 | } 42 | }) 43 | } 44 | }; -------------------------------------------------------------------------------- /lib/gitDiffHandler.js: -------------------------------------------------------------------------------- 1 | const child_process = require('child_process'); 2 | const cleanUpRepo = require('./cleanUpRepo'); 3 | 4 | module.exports = function(config) { 5 | 6 | this.diff = function() { 7 | return new Promise(function(resolve, reject) { 8 | let fullResult = ''; 9 | if(!config.from) { 10 | const firstCommitSHARaw = child_process.spawnSync('git', ['rev-list', '--max-parents=0', 'HEAD'],{ 11 | "cwd": config.repo 12 | }).stdout; 13 | const firstCommitSHA = Buffer.from(firstCommitSHARaw); 14 | config.from = firstCommitSHA.toString('utf8').trim(); 15 | } 16 | const child = child_process.spawn("git", ["diff", "--name-status", config.from, config.to], { 17 | "cwd": config.repo 18 | }); 19 | child.stdout.on('data', function(data) { 20 | let buff = Buffer.from(data); 21 | fullResult += buff.toString('utf8'); 22 | }); 23 | child.on('close', function(code) { 24 | const diffs = { 25 | 'package.xml' : {}, 26 | 'destructiveChangesPre.xml' : {} 27 | }, 28 | lines = fullResult.split('\n'); 29 | lines.forEach(function(line) { 30 | let context = null; 31 | if (line.indexOf('D') !== 0) { 32 | context = diffs['package.xml']; 33 | } else { 34 | context = diffs['destructiveChangesPre.xml']; 35 | } 36 | let explodedLine = line.split('/'); 37 | if (explodedLine[0].indexOf('src') !== -1 && !explodedLine[explodedLine.length-1].endsWith('.xml')) { 38 | if (context[explodedLine[1]] === undefined) { 39 | context[explodedLine[1]] = []; 40 | } 41 | let elementName = explodedLine.slice(2).join('/').split('.').slice(0,-1).join('.'); 42 | if(explodedLine[1] == 'aura') { 43 | elementName = elementName.split('/').shift(); 44 | } 45 | if(context[explodedLine[1]].indexOf(elementName) === -1) { 46 | context[explodedLine[1]].push(elementName); 47 | } 48 | } 49 | }); 50 | // TODO handle Custom Object Contents (fields, fieldsets, weblink), workflows and Custom Labels remove 51 | const cur = new cleanUpRepo(config,diffs['package.xml']); 52 | cur.cleanUpRepo(); 53 | 54 | resolve(diffs); 55 | }); 56 | child.stderr.on("data", function(data) { 57 | var buff = new Buffer(data); 58 | reject(buff.toString('utf8')); 59 | }); 60 | }); 61 | }; 62 | }; -------------------------------------------------------------------------------- /lib/packageConstructor.js: -------------------------------------------------------------------------------- 1 | const xmlbuilder = require('xmlbuilder'); 2 | const metadata = require('./utils/metadata'); 3 | 4 | module.exports = function(config) { 5 | this.constructPackage = structuredDiffs => { 6 | return new Promise((resolve,reject) => { 7 | let filesContent = {}; 8 | const sorted_packageType = Object.keys(structuredDiffs).sort(); 9 | sorted_packageType.forEach(packageType => { 10 | const strucDiffPerType = structuredDiffs[packageType]; 11 | let xml = xmlbuilder.create('Package') 12 | .att('xmlns', 'http://soap.sforce.com/2006/04/metadata') 13 | .dec('1.0', 'UTF-8'); 14 | for(let structuredDiff in strucDiffPerType) { 15 | if(metadata[structuredDiff] !== undefined) { 16 | // Handle different type of package.xml build 17 | let type = xml.ele('types'); 18 | for(var elem in strucDiffPerType[structuredDiff]) if(!!elem) { 19 | type.ele('members') 20 | .t(strucDiffPerType[structuredDiff][elem]); 21 | } 22 | type.ele('name').t(metadata[structuredDiff].xmlName); 23 | } 24 | } 25 | xml.ele('version') 26 | .t(config.apiVersion); 27 | filesContent[packageType] = xml.end({ pretty: true, indent: ' ', newline: '\n' }); 28 | }); 29 | resolve(filesContent); 30 | }); 31 | }; 32 | }; -------------------------------------------------------------------------------- /lib/utils/fileUtils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | module.exports = function(config){ 4 | this.writeAsync = function (filesContent){ 5 | const filesContentArray = Object.keys(filesContent).map(function (key) { 6 | return { 7 | 'fileName' : key, 8 | 'content' : filesContent[key] 9 | } 10 | }); 11 | return Promise.all(filesContentArray.map(function(fileContent) { 12 | return new Promise(function(resolve,reject) { 13 | fs.writeFile(config.output + '/' + fileContent.fileName, fileContent.content, 'utf8', function(err) { 14 | err && reject(err); 15 | resolve(config.output + '/' + fileContent.fileName); 16 | }); 17 | }); 18 | })); 19 | } 20 | }; -------------------------------------------------------------------------------- /lib/utils/metadata.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /*"installedPackages":{ 3 | "xmlName":"InstalledPackage", 4 | "children":{ 5 | } 6 | },*/ 7 | "labels":{ 8 | "xmlName":"CustomLabels", 9 | "children":{ 10 | "CustomLabels":"CustomLabel" 11 | } 12 | }, 13 | "staticresources":{ 14 | "xmlName":"StaticResource", 15 | "children":{ 16 | 17 | } 18 | }, 19 | "scontrols":{ 20 | "xmlName":"Scontrol", 21 | "children":{ 22 | 23 | } 24 | }, 25 | "components":{ 26 | "xmlName":"ApexComponent", 27 | "children":{ 28 | 29 | } 30 | }, 31 | "customMetadata":{ 32 | "xmlName":"CustomMetadata", 33 | "children":{ 34 | 35 | } 36 | }, 37 | "globalValueSets":{ 38 | "xmlName":"GlobalValueSet", 39 | "children":{ 40 | 41 | } 42 | }, 43 | "globalValueSetTranslations":{ 44 | "xmlName":"GlobalValueSetTranslation", 45 | "children":{ 46 | 47 | } 48 | }, 49 | "standardValueSets":{ 50 | "xmlName":"StandardValueSet", 51 | "children":{ 52 | 53 | } 54 | }, 55 | "pages":{ 56 | "xmlName":"ApexPage", 57 | "children":{ 58 | 59 | } 60 | }, 61 | "queues":{ 62 | "xmlName":"Queue", 63 | "children":{ 64 | 65 | } 66 | }, 67 | "objects":{ 68 | "xmlName":"CustomObject", 69 | "children":{ 70 | "actionOverrides":{"typeName":"ActionOverride","name":"actionName"}, 71 | "fields":{"typeName":"CustomField","name":"fullName"}, 72 | "businessProcesses":{"typeName":"BusinessProcess","name":"fullName"}, 73 | "recordTypes":{"typeName":"RecordType","name":"fullName"}, 74 | "webLinks":{"typeName":"WebLink","name":"fullName"}, 75 | "validationRules":{"typeName":"ValidationRule","name":"fullName"}, 76 | "namedFilters":{"typeName":"NamedFilter","name":"fullName"}, 77 | "sharingReasons":{"typeName":"SharingReason","name":"fullName"}, 78 | "listViews":{"typeName":"ListView","name":"fullName"}, 79 | "fieldSets":{"typeName":"FieldSet","name":"fullName"}, 80 | "compactLayouts":{"typeName":"CompactLayout","name":"fullName"} 81 | } 82 | }, 83 | "reportTypes":{ 84 | "xmlName":"ReportType", 85 | "children":{ 86 | 87 | } 88 | }, 89 | "reports":{ 90 | "xmlName":"Report", 91 | "children":{ 92 | 93 | } 94 | }, 95 | "dashboards":{ 96 | "xmlName":"Dashboard", 97 | "children":{ 98 | 99 | } 100 | }, 101 | "analyticSnapshots":{ 102 | "xmlName":"AnalyticSnapshot", 103 | "children":{ 104 | 105 | } 106 | }, 107 | "layouts":{ 108 | "xmlName":"Layout", 109 | "children":{ 110 | 111 | } 112 | }, 113 | "portals":{ 114 | "xmlName":"Portal", 115 | "children":{ 116 | 117 | } 118 | }, 119 | "documents":{ 120 | "xmlName":"Document", 121 | "children":{ 122 | 123 | } 124 | }, 125 | "weblinks":{ 126 | "xmlName":"CustomPageWebLink", 127 | "children":{ 128 | 129 | } 130 | }, 131 | "quickActions":{ 132 | "xmlName":"QuickAction", 133 | "children":{ 134 | 135 | } 136 | }, 137 | "flexipages":{ 138 | "xmlName":"FlexiPage", 139 | "children":{ 140 | 141 | } 142 | }, 143 | "tabs":{ 144 | "xmlName":"CustomTab", 145 | "children":{ 146 | 147 | } 148 | }, 149 | "customApplicationComponents":{ 150 | "xmlName":"CustomApplicationComponent", 151 | "children":{ 152 | 153 | } 154 | }, 155 | "applications":{ 156 | "xmlName":"CustomApplication", 157 | "children":{ 158 | 159 | } 160 | }, 161 | "letterhead":{ 162 | "xmlName":"Letterhead", 163 | "children":{ 164 | 165 | } 166 | }, 167 | "email":{ 168 | "xmlName":"EmailTemplate", 169 | "children":{ 170 | 171 | } 172 | }, 173 | "workflows":{ 174 | "xmlName":"Workflow", 175 | "children":{ 176 | "alerts":{"typeName":"WorkflowAlert","name":"fullName"}, 177 | "tasks":{"typeName" : "WorkflowTask", "name" : "fullName"}, 178 | "outboundMessages":{"typeName" : "WorkflowOutboundMessage","name" : "fullName"}, 179 | "fieldUpdates":{"typeName" : "WorkflowFieldUpdate", "name" : "fullName"}, 180 | "rules":{"typeName" : "WorkflowRule", "name" : "fullName"}, 181 | "emailRecipients":{"typeName" : "WorkflowEmailRecipient", "name" : "fullName"}, 182 | "timeTriggers":{"typeName" : "WorkflowTimeTrigger", "name" : "fullName"}, 183 | "actionReferences":{"typeName" : "WorkflowActionReference", "name" : "fullName"} 184 | } 185 | }, 186 | "assignmentRules":{ 187 | "xmlName":"AssignmentRules", 188 | "children":{ 189 | 190 | } 191 | }, 192 | "autoResponseRules":{ 193 | "xmlName":"AutoResponseRules", 194 | "children":{ 195 | 196 | } 197 | }, 198 | "escalationRules":{ 199 | "xmlName":"EscalationRules", 200 | "children":{ 201 | 202 | } 203 | }, 204 | "roles":{ 205 | "xmlName":"Role", 206 | "children":{ 207 | 208 | } 209 | }, 210 | "groups":{ 211 | "xmlName":"Group", 212 | "children":{ 213 | 214 | } 215 | }, 216 | "postTemplates":{ 217 | "xmlName":"PostTemplate", 218 | "children":{ 219 | 220 | } 221 | }, 222 | "approvalProcesses":{ 223 | "xmlName":"ApprovalProcess", 224 | "children":{ 225 | 226 | } 227 | }, 228 | "homePageComponents":{ 229 | "xmlName":"HomePageComponent", 230 | "children":{ 231 | 232 | } 233 | }, 234 | "homePageLayouts":{ 235 | "xmlName":"HomePageLayout", 236 | "children":{ 237 | 238 | } 239 | }, 240 | "objectTranslations":{ 241 | "xmlName":"CustomObjectTranslation", 242 | "children":{ 243 | 244 | } 245 | }, 246 | "flows":{ 247 | "xmlName":"Flow", 248 | "children":{ 249 | 250 | } 251 | }, 252 | "classes":{ 253 | "xmlName":"ApexClass", 254 | "children":{ 255 | 256 | } 257 | }, 258 | "triggers":{ 259 | "xmlName":"ApexTrigger", 260 | "children":{ 261 | 262 | } 263 | }, 264 | "profiles":{ 265 | "xmlName":"Profile", 266 | "children":{ 267 | 268 | } 269 | }, 270 | "permissionsets":{ 271 | "xmlName":"PermissionSet", 272 | "children":{ 273 | 274 | } 275 | }, 276 | "datacategorygroups":{ 277 | "xmlName":"DataCategoryGroup", 278 | "children":{ 279 | 280 | } 281 | }, 282 | "remoteSiteSettings":{ 283 | "xmlName":"RemoteSiteSetting", 284 | "children":{ 285 | 286 | } 287 | }, 288 | "authproviders":{ 289 | "xmlName":"AuthProvider", 290 | "children":{ 291 | 292 | } 293 | }, 294 | "leadSharingRules":{ 295 | "xmlName":"LeadSharingRules", 296 | "children":{ 297 | 298 | } 299 | }, 300 | "campaignSharingRules":{ 301 | "xmlName":"CampaignSharingRules", 302 | "children":{ 303 | 304 | } 305 | }, 306 | "caseSharingRules":{ 307 | "xmlName":"CaseSharingRules", 308 | "children":{ 309 | 310 | } 311 | }, 312 | "contactSharingRules":{ 313 | "xmlName":"ContactSharingRules", 314 | "children":{ 315 | 316 | } 317 | }, 318 | "opportunitySharingRules":{ 319 | "xmlName":"OpportunitySharingRules", 320 | "children":{ 321 | 322 | } 323 | }, 324 | "accountSharingRules":{ 325 | "xmlName":"AccountSharingRules", 326 | "children":{ 327 | 328 | } 329 | }, 330 | "customObjectSharingRules":{ 331 | "xmlName":"CustomObjectSharingRules", 332 | "children":{ 333 | 334 | } 335 | }, 336 | "communities":{ 337 | "xmlName":"Community", 338 | "children":{ 339 | 340 | } 341 | }, 342 | "callCenters":{ 343 | "xmlName":"CallCenter", 344 | "children":{ 345 | 346 | } 347 | }, 348 | "connectedApps":{ 349 | "xmlName":"ConnectedApp", 350 | "children":{ 351 | 352 | } 353 | }, 354 | "samlssoconfigs":{ 355 | "xmlName":"SamlSsoConfig", 356 | "children":{ 357 | 358 | } 359 | }, 360 | "synonymDictionaries":{ 361 | "xmlName":"SynonymDictionary", 362 | "children":{ 363 | 364 | } 365 | }, 366 | "settings":{ 367 | "xmlName":"Settings", 368 | "children":{ 369 | 370 | } 371 | }, 372 | "aura":{ 373 | "xmlName":"AuraDefinitionBundle", 374 | "children":{ 375 | 376 | } 377 | }, 378 | "sharingRules":{ 379 | "xmlName":"SharingRules", 380 | "children":{ 381 | "sharingTerritoryRules":"SharingTerritoryRule", 382 | "sharingOwnerRules":"SharingOwnerRule", 383 | "sharingCriteriaRules":"SharingCriteriaRule" 384 | } 385 | }, 386 | "contentassets":{ 387 | "xmlName":"ContentAsset", 388 | "children":{ 389 | 390 | } 391 | }, 392 | "networks":{ 393 | "xmlName":"Network", 394 | "children":{ 395 | 396 | } 397 | }, 398 | "siteDotComSites":{ 399 | "xmlName":"SiteDotCom", 400 | "children":{ 401 | 402 | } 403 | }, 404 | "flowDefinitions":{ 405 | "xmlName":"FlowDefinition", 406 | "children":{ 407 | 408 | } 409 | }, 410 | "matchingRules":{ 411 | "xmlName":"MatchingRules", 412 | "children":{ 413 | 414 | } 415 | } 416 | }; 417 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sfdc-git-package", 3 | "version": "2.0.8", 4 | "description": "Create Package.xml and destructiveChangesPre.xml from git diff between two commits", 5 | "keyword": [ 6 | "salesforce", 7 | "package", 8 | "git", 9 | "cli" 10 | ], 11 | "main": "index.js", 12 | "bin": { 13 | "sgp": "./bin/cli" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://ForceComDeveloper@github.com/scolladon/sfdc-git-package.git" 18 | }, 19 | "author": "Sebastien Colladon ", 20 | "dependencies": { 21 | "commander": "2.9.0", 22 | "xmlbuilder": "~2.6.1" 23 | }, 24 | "license": "MIT" 25 | } 26 | --------------------------------------------------------------------------------