Skip to content

Commit 076e977

Browse files
author
Gil Avignon
committed
Initial commit
0 parents  commit 076e977

File tree

12 files changed

+881
-0
lines changed

12 files changed

+881
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.DS_Store
2+
node_modules/
3+
files/**/*.json

LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2017 Gil Avignon
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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# sfdc-generate-data-dictionary
2+
3+
generate data dictionary from a Salesforce Org
4+
5+
## Getting Started
6+
7+
Works in Unix like system. Windows is not tested.
8+
9+
### Installing
10+
11+
```
12+
npm install -g sfdc-generate-data-dictionary
13+
```
14+
15+
## Usage
16+
17+
### Command Line
18+
19+
```
20+
$ sgd -h
21+
22+
Usage: sgd [options]
23+
24+
generate data dictionary from a Salesforce Org
25+
26+
Options:
27+
28+
-u, --username salesforce username
29+
-p, --password salesforce user password
30+
-l, --loginUrl salesforce login URL [https://login.salesforce.com]
31+
-c, --customObjects retrieve all custom objects [true]
32+
-d, --deleteFolders delete/clean temp folders [true]
33+
-o, --output salesforce data dictionary directory path [.]
34+
```
35+
36+
### Module
37+
38+
```
39+
var sgd = require('sfdc-generate-data-dictionary');
40+
41+
sgd({
42+
'username': '',
43+
'password': options.password,
44+
'loginUrl': options.loginUrl,
45+
'projectName': '',
46+
'allCustomObjects': true,
47+
'cleanFolders': true,
48+
'output':'.'
49+
}, console.log);
50+
```
51+
52+
## Built With
53+
54+
- [commander](https://github.com/tj/commander.js/) - The complete solution for node.js command-line interfaces, inspired by Ruby's commander.
55+
- [bytes](https://github.com/visionmedia/bytes.js) - Utility to parse a string bytes to bytes and vice-versa.
56+
- [excel4node](https://github.com/amekkawi/excel4node) - Node module to allow for easy Excel file creation.
57+
- [jsforce](https://github.com/jsforce/jsforce) - Salesforce API Library for JavaScript applications (both on Node.js and web browser)
58+
59+
## Versioning
60+
61+
[SemVer](http://semver.org/) is used for versioning.
62+
63+
## Authors
64+
65+
- **Gil Avignon** - _Initial work_ - [gavignon](https://github.com/gavignon)
66+
67+
## License
68+
69+
This project is licensed under the MIT License - see the <LICENSE.md> file for details

bin/cli

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env node
2+
'use strict';
3+
4+
const program = require('commander');
5+
const orchestrator = require('../index.js');
6+
const pjson = require('../package.json');
7+
8+
program
9+
.description(pjson.description)
10+
.version(pjson.version)
11+
.option('-u, --username [username]', 'salesforce username')
12+
.option('-p, --password [password]', 'salesforce password')
13+
.option('-l, --loginUrl [loginUrl]', 'salesforce login URL [https://login.salesforce.com]', 'https://login.salesforce.com')
14+
.option('-c, --customObjects [customObjects]', 'retrieve all custom objects [true]', true)
15+
.option('-d, --deleteFolders [deleteFolders]', 'delete/clean temp folders [true]', true)
16+
.option('-o, --output [dir]', 'salesforce data dictionary directory path [.]', '.')
17+
.parse(process.argv);
18+
19+
orchestrator(program, console.log)
20+
.catch(function(err){
21+
throw err;
22+
});

files/describe/.gitkeep

Whitespace-only changes.

files/metadata/.gitkeep

Whitespace-only changes.

files/tooling/.gitkeep

Whitespace-only changes.

index.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
'use strict';
2+
const jsforce = require('jsforce');
3+
const Downloader = require('./lib/downloader.js');
4+
const ExcelBuilder = require('./lib/excelbuilder.js');
5+
const Utils = require('./lib/utils.js');
6+
7+
module.exports = (config, logger) => {
8+
// Check all mandatory config options
9+
if (typeof config.username === 'undefined' || config.username === null ||
10+
typeof config.password === 'undefined' || config.password === null) {
11+
throw new Error('Not enough config options');
12+
}
13+
14+
// Set default values
15+
if (typeof config.loginUrl === 'undefined' || config.loginUrl === null) {
16+
config.loginUrl = 'https://login.salesforce.com';
17+
}
18+
if (typeof config.output === 'undefined' || config.output === null) {
19+
config.output = '.';
20+
}
21+
if (typeof config.projectName === 'undefined' || config.projectName === null) {
22+
config.projectName = 'PROJECT';
23+
}
24+
if (typeof config.allCustomObjects === 'undefined' || config.allCustomObjects === null) {
25+
config.allCustomObjects = true;
26+
}
27+
if (typeof config.objects === 'undefined' || config.objects === null) {
28+
config.objects = [
29+
'Account',
30+
'Contact'
31+
];
32+
}
33+
if (typeof config.columns === 'undefined' || config.columns === null) {
34+
config.columns = {
35+
'ReadOnly': 5,
36+
'Mandatory': 3,
37+
'Name': 25,
38+
'Description': 90,
39+
'APIName': 25,
40+
'Type': 27,
41+
'Values': 45
42+
};
43+
}
44+
45+
// Clean folders that contain API files
46+
if (config.cleanFolders) {
47+
let utils = new Utils();
48+
const statusRmDescribe = utils.rmDir(__dirname + '/files/describe', '.json', false);
49+
const statusRmMetadata = utils.rmDir(__dirname + '/files/metadata', '.json', false);
50+
logger('File folders cleaned');
51+
}
52+
53+
// Main promise
54+
const promise = new Promise((resolve, reject) => {
55+
56+
const conn = new jsforce.Connection({
57+
loginUrl: config.loginUrl
58+
});
59+
60+
// Salesforce connection
61+
conn.login(config.username, config.password).then(result => {
62+
logger('Connected as ' + config.username);
63+
if (config.allCustomObjects) {
64+
conn.describeGlobal().then(res => {
65+
for (let i = 0; i < res.sobjects.length; i++) {
66+
let object = res.sobjects[i];
67+
if (config.objects === undefined)
68+
config.objects = [];
69+
70+
// If the sObject is a real custom object
71+
if (object.custom && (object.name.indexOf('__c') !== -1) && (object.name.split('__').length - 1 < 2))
72+
config.objects.push(object.name);
73+
}
74+
75+
const downloader = new Downloader(config, logger, conn);
76+
const builder = new ExcelBuilder(config, logger);
77+
78+
// Download metadata files
79+
downloader.execute().then(result => {
80+
logger(result + ' downloaded');
81+
// Generate the excel file
82+
builder.generate().then(result => {
83+
resolve();
84+
});
85+
})
86+
});
87+
} else {
88+
if (config.objects.length > 0) {
89+
const downloader = new Downloader(config, logger, conn);
90+
const builder = new ExcelBuilder(config, logger);
91+
92+
// Download metadata files
93+
downloader.execute().then(result => {
94+
logger(result + ' downloaded');
95+
// Generate the excel file
96+
builder.generate().then(result => {
97+
resolve();
98+
});
99+
})
100+
}
101+
}
102+
}).catch(reject);
103+
});
104+
return promise;
105+
};

lib/downloader.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const bytes = require('bytes');
4+
const Utils = require('./utils.js');
5+
6+
const FILE_DIR = '../files';
7+
8+
module.exports = class Downloader {
9+
constructor(config, logger, conn) {
10+
this.config = config;
11+
this.logger = logger;
12+
this.conn = conn;
13+
this.utils = new Utils(logger);
14+
}
15+
16+
downloadDescribe(sObject) {
17+
const self = this;
18+
return new Promise((resolve, reject) => {
19+
self.conn.sobject(sObject).describe().then(meta => {
20+
const filePath = path.join(__dirname, FILE_DIR, '/describe/', sObject + '.json');
21+
fs.writeFileSync(filePath, JSON.stringify(meta.fields), 'utf-8');
22+
const stats = fs.statSync(filePath);
23+
24+
resolve(stats.size);
25+
});
26+
});
27+
}
28+
29+
downloadMetadata(sobjectList) {
30+
const self = this;
31+
return new Promise((resolve, reject) => {
32+
self.conn.metadata.read('CustomObject', sobjectList).then(metadata => {
33+
let filePath = '';
34+
35+
if (sobjectList.length === 1) {
36+
let fields = metadata.fields;
37+
fields.sort(self.utils.sortByProperty('fullName'));
38+
filePath = path.join(__dirname, FILE_DIR, '/metadata/', metadata.fullName + '.json');
39+
fs.writeFileSync(filePath, JSON.stringify(metadata), 'utf-8');
40+
} else {
41+
for (let i = 0; i < metadata.length; i++) {
42+
let fields = metadata[i].fields;
43+
if (Array.isArray(fields) && (fields !== undefined || fields.length > 0)) {
44+
fields.sort(self.utils.sortByProperty('fullName'));
45+
filePath = path.join(__dirname, FILE_DIR, '/metadata/', metadata[i].fullName + '.json');
46+
fs.writeFileSync(filePath, JSON.stringify(metadata[i]), 'utf-8');
47+
} else {
48+
self.config.objects.splice(self.config.objects.indexOf(metadata[i]), 1);
49+
}
50+
}
51+
}
52+
const stats = fs.statSync(filePath);
53+
54+
resolve(stats.size);
55+
}).catch(function(err) {
56+
console.log(err.message);
57+
console.log(err.stack);
58+
});
59+
});
60+
}
61+
62+
execute() {
63+
const promise = new Promise((resolve, reject) => {
64+
const self = this;
65+
66+
this.logger('Downloading...');
67+
68+
let downloadArray = new Array();
69+
70+
for (let object of self.config.objects) {
71+
downloadArray.push(self.downloadDescribe(object));
72+
}
73+
74+
let loop = ~~(self.config.objects.length / 10);
75+
if (self.config.objects.length % 10 > 0)
76+
loop++;
77+
78+
let j = 0;
79+
for (let i = 0; i < loop; i++) {
80+
let objectList = self.config.objects.slice(j, j + 10);
81+
j += 10;
82+
downloadArray.push(self.downloadMetadata(objectList));
83+
}
84+
85+
Promise.all(
86+
downloadArray
87+
).then(results => {
88+
let total = 0;
89+
for (let fileSize of results) {
90+
total += fileSize;
91+
}
92+
resolve(bytes.format(total, {
93+
decimalPlaces: 2
94+
}));
95+
}).catch(reject);
96+
});
97+
return promise;
98+
}
99+
}

0 commit comments

Comments
 (0)