file for details
106 |
--------------------------------------------------------------------------------
/bin/cli:
--------------------------------------------------------------------------------
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('-a, --apiVersion [apiVersion]', 'salesforce API Version [48.0]', '48.0')
15 | .option('-c, --allCustomObjects [allCustomObjects]', 'retrieve all custom objects [true]', true)
16 | .option('-lc, --lucidchart [lucidchart]', 'generate ERD file for Lucidchart [true]', true)
17 | .option('-s, --sobjects [sobjects]', 'sObjects to retrieve separated with commas')
18 | .option('-D, --debug [debug]', 'generate debug log file [false]', false)
19 | .option('-e, --excludeManagedPackage [excludeManagedPackage]', 'exclude managed packaged [true]', true)
20 | .option('-d, --deleteFolders [deleteFolders]', 'delete/clean temp folders [true]', true)
21 | .option('-ht, --hideTechFields [hideTechFields]', 'hide tech fields', false)
22 | .option('-tp, --techFieldPrefix [techFieldPrefix]', 'Tech field prefix', 'TECH_')
23 | .option('-t, --outputTime [outputTime]', 'Display Hours in the file name', false)
24 | .option('-o, --output [dir]', 'salesforce data dictionary directory path [.]', '.')
25 | .parse(process.argv);
26 |
27 | orchestrator(program, console.log)
28 | .catch(function(err){
29 | throw err;
30 | });
31 |
--------------------------------------------------------------------------------
/files/describe/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavignon/sfdc-generate-data-dictionary/bd7d40a957aaa1b75b8140cd7dde318aa5d4e891/files/describe/.gitkeep
--------------------------------------------------------------------------------
/files/metadata/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavignon/sfdc-generate-data-dictionary/bd7d40a957aaa1b75b8140cd7dde318aa5d4e891/files/metadata/.gitkeep
--------------------------------------------------------------------------------
/files/tooling/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavignon/sfdc-generate-data-dictionary/bd7d40a957aaa1b75b8140cd7dde318aa5d4e891/files/tooling/.gitkeep
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
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 |
9 |
10 |
11 | // Check all mandatory config options
12 | if (typeof config.username === 'undefined' || config.username === null ||
13 | typeof config.password === 'undefined' || config.password === null) {
14 | throw new Error('Not enough config options');
15 | }
16 |
17 | // Set default values
18 | if (typeof config.loginUrl === 'undefined' || config.loginUrl === null) {
19 | config.loginUrl = 'https://login.salesforce.com';
20 | }
21 | if (typeof config.apiVersion === 'undefined' || config.apiVersion === null) {
22 | config.apiVersion = '48.0';
23 | }
24 | if (typeof config.output === 'undefined' || config.output === null) {
25 | config.output = '.';
26 | }
27 | if (typeof config.debug === 'undefined' || config.debug === null) {
28 | config.debug = false;
29 | }
30 | config.debug = (config.debug === "true" || config.debug === true);
31 |
32 | if (typeof config.excludeManagedPackage === 'undefined' || config.excludeManagedPackage === null) {
33 | config.excludeManagedPackage = true;
34 | }
35 | config.excludeManagedPackage = (config.excludeManagedPackage === "true" || config.excludeManagedPackage === true);
36 |
37 | if (typeof config.projectName === 'undefined' || config.projectName === null) {
38 | config.projectName = 'PROJECT';
39 | }
40 | if (typeof config.outputTime === 'undefined' || config.outputTime === null) {
41 | config.outputTime = false;
42 | }
43 | if (typeof config.allCustomObjects === 'undefined' || config.allCustomObjects === null) {
44 | config.allCustomObjects = true;
45 | }
46 | config.allCustomObjects = (config.allCustomObjects === "true" || config.allCustomObjects === true);
47 |
48 | if (typeof config.lucidchart === 'undefined' || config.lucidchart === null) {
49 | config.lucidchart = true;
50 | }
51 | config.lucidchart = (config.lucidchart === "true" || config.lucidchart === true);
52 |
53 | if (typeof config.sobjects === 'undefined' || config.sobjects === null) {
54 | config.objects = [
55 | 'Account',
56 | 'Contact',
57 | 'User'
58 | ];
59 | } else {
60 | // If an array is passed to the module
61 | if (Array.isArray(config.sobjects)) {
62 | config.objects = config.sobjects;
63 | } else {
64 | // Check and parse standObjects string for command-line
65 | try {
66 | config.objects = config.sobjects.split(',');
67 | } catch (e) {
68 | let errorMessage = 'Unable to parse sobjects parameter';
69 | if (config.debug)
70 | errorMessage += ' : ' + e;
71 | throw new Error(errorMessage);
72 | }
73 | }
74 | }
75 |
76 |
77 | if (typeof config.techFieldPrefix === 'undefined' || config.techFieldPrefix === null) {
78 | config.techFieldPrefix = 'TECH_';
79 | }
80 | if (typeof config.hideTechFields === 'undefined' || config.hideTechFields === null) {
81 | config.hideTechFields = false;
82 | }
83 | if (typeof config.columns === 'undefined' || config.columns === null) {
84 | config.columns = {
85 | 'ReadOnly': 5,
86 | 'Mandatory': 3,
87 | 'Name': 25,
88 | 'Description': 90,
89 | 'Helptext': 90,
90 | 'APIName': 25,
91 | 'Type': 27,
92 | 'Values': 45
93 | };
94 | }
95 |
96 | var utils = new Utils();
97 |
98 | // Clean folders that contain API files
99 | if (config.cleanFolders) {
100 | const statusRmDescribe = utils.rmDir(__dirname + '/files/describe', '.json', false);
101 | const statusRmMetadata = utils.rmDir(__dirname + '/files/metadata', '.json', false);
102 | logger('File folders cleaned');
103 | }
104 |
105 | // Main promise
106 | const promise = new Promise((resolve, reject) => {
107 |
108 | const conn = new jsforce.Connection({
109 | loginUrl: config.loginUrl,
110 | version: config.apiVersion
111 | });
112 |
113 | // Salesforce connection
114 | conn.login(config.username, config.password).then(result => {
115 | logger('Connected as ' + config.username);
116 | if (config.debug) {
117 | utils.log('Connected as ' + config.username, config);
118 | }
119 |
120 | if (config.allCustomObjects) {
121 | conn.describeGlobal().then(res => {
122 | for (let i = 0; i < res.sobjects.length; i++) {
123 | let object = res.sobjects[i];
124 | if (config.objects === undefined)
125 | config.objects = [];
126 |
127 | // If the sObject is a real custom object
128 | if (object.custom && (object.name.indexOf('__c') !== -1)) {
129 | if (config.debug)
130 | utils.log('# excludeManagedPackage (' + config.excludeManagedPackage + '): ' + object.name, config);
131 |
132 | if (config.excludeManagedPackage) {
133 | if ((object.name.split('__').length - 1 < 2))
134 | config.objects.push(object.name);
135 | } else {
136 | config.objects.push(object.name);
137 | }
138 | }
139 | }
140 |
141 | if (config.debug)
142 | utils.log(JSON.stringify(config.objects), config);
143 |
144 | const downloader = new Downloader(config, logger, conn);
145 | const builder = new ExcelBuilder(config, logger);
146 |
147 | // Download metadata files
148 | downloader.execute().then(result => {
149 | logger(result + ' downloaded');
150 | // Generate the excel file
151 | builder.generate().then(result => {
152 | resolve();
153 | });
154 | })
155 | });
156 | } else {
157 | if (config.objects.length > 0) {
158 | const downloader = new Downloader(config, logger, conn);
159 | const builder = new ExcelBuilder(config, logger);
160 |
161 | // Download metadata files
162 | downloader.execute().then(result => {
163 | logger(result + ' downloaded');
164 | // Generate the excel file
165 | return builder.generate();
166 |
167 | }).then(result => {
168 | resolve();
169 | });
170 |
171 | }
172 | }
173 | }).catch(reject);
174 | });
175 | return promise;
176 | };
177 |
--------------------------------------------------------------------------------
/lib/downloader.js:
--------------------------------------------------------------------------------
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 | }).catch(function(err) {
26 | reject(sObject + ': ' + err);
27 | if (self.config.debug) {
28 | self.utils.log(err, self.config);
29 | }
30 | });
31 | });
32 | }
33 |
34 | downloadMetadata(sobjectList) {
35 | const self = this;
36 | return new Promise((resolve, reject) => {
37 | self.conn.metadata.read('CustomObject', sobjectList).then(metadata => {
38 | let filePath = '';
39 |
40 |
41 |
42 | if (sobjectList.length === 1) {
43 | let fields = metadata.fields;
44 | fields.sort(self.utils.sortByProperty('fullName'));
45 | filePath = path.join(__dirname, FILE_DIR, '/metadata/', metadata.fullName + '.json');
46 | fs.writeFileSync(filePath, JSON.stringify(metadata), 'utf-8');
47 | } else {
48 | for (let i = 0; i < metadata.length; i++) {
49 |
50 | let fields = metadata[i].fields;
51 | if ((!Array.isArray(fields) || (Array.isArray(fields) && (fields !== undefined || fields.length > 0)))) {
52 | // Manage single object or an object array
53 | if(fields != null && !Array.isArray(fields)){
54 | let fieldsArray = new Array();
55 | fieldsArray.push(fields);
56 | fields = fieldsArray;
57 | metadata[i].fields = fields;
58 | }
59 |
60 | filePath = path.join(__dirname, FILE_DIR, '/metadata/', metadata[i].fullName + '.json');
61 | fs.writeFileSync(filePath, JSON.stringify(metadata[i]), 'utf-8');
62 | } else {
63 | self.config.objects.splice(self.config.objects.indexOf(metadata[i]), 1);
64 | }
65 | }
66 | }
67 | const stats = fs.statSync(filePath);
68 |
69 | resolve(stats.size);
70 | }).catch(function(err) {
71 | reject(err);
72 | if (self.config.debug) {
73 | self.utils.log(err, self.config);
74 | }
75 | });
76 | });
77 | }
78 |
79 | execute() {
80 | const promise = new Promise((resolve, reject) => {
81 | const self = this;
82 |
83 | this.logger('Downloading...');
84 |
85 | let downloadArray = new Array();
86 |
87 | for (let object of self.config.objects) {
88 | downloadArray.push(self.downloadDescribe(object));
89 | }
90 |
91 | let loop = ~~(self.config.objects.length / 10);
92 | if (self.config.objects.length % 10 > 0)
93 | loop++;
94 |
95 | let j = 0;
96 | for (let i = 0; i < loop; i++) {
97 | let objectList = self.config.objects.slice(j, j + 10);
98 | j += 10;
99 | downloadArray.push(self.downloadMetadata(objectList));
100 | }
101 |
102 | Promise.all(
103 | downloadArray
104 | ).then(results => {
105 | let total = 0;
106 | for (let fileSize of results) {
107 | total += fileSize;
108 | }
109 | resolve(bytes.format(total, {
110 | decimalPlaces: 2
111 | }));
112 | }).catch(err => {
113 | if (self.config.debug) {
114 | self.utils.log(err, self.config);
115 | }
116 | self.logger(err);
117 | });
118 | });
119 | return promise;
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/lib/excelbuilder.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const excel = require('excel4node');
3 | const path = require('path');
4 | const Utils = require('./utils.js');
5 |
6 | const FILE_DIR = '../files';
7 | const MAX_PICKLIST_VALUES = 2;
8 |
9 | // Styles
10 | var workbook = new excel.Workbook();
11 | var startGeneration;
12 |
13 | var global = workbook.createStyle({
14 | font: {
15 | size: 12
16 | },
17 | alignment: {
18 | wrapText: true,
19 | vertical: 'center',
20 | },
21 | border: {
22 | left: {
23 | style: 'thin',
24 | color: 'b8b6b8'
25 | },
26 | right: {
27 | style: 'thin',
28 | color: 'b8b6b8'
29 | },
30 | top: {
31 | style: 'thin',
32 | color: 'b8b6b8'
33 | },
34 | bottom: {
35 | style: 'thin',
36 | color: 'b8b6b8'
37 | }
38 | }
39 | });
40 |
41 | var header = workbook.createStyle({
42 | font: {
43 | bold: true,
44 | color: 'FFFFFF'
45 | },
46 | alignment: {
47 | horizontal: 'center'
48 | },
49 | fill: {
50 | type: 'pattern',
51 | patternType: 'solid',
52 | fgColor: '019cdd'
53 | }
54 | });
55 |
56 | var subHeader = workbook.createStyle({
57 | font: {
58 | bold: true
59 | },
60 | fill: {
61 | type: 'pattern',
62 | patternType: 'solid',
63 | fgColor: 'F5F4F2' // HTML style hex value. optional. defaults to black
64 | }
65 | });
66 |
67 | var category = workbook.createStyle({
68 | font: {
69 | // bold: true,
70 | color: '60809f'
71 | },
72 | fill: {
73 | type: 'pattern',
74 | patternType: 'solid',
75 | fgColor: 'dbeaf7'
76 | }
77 | });
78 |
79 | var validationCategory = workbook.createStyle({
80 | font: {
81 | // bold: true,
82 | color: '703026'
83 | },
84 | fill: {
85 | type: 'pattern',
86 | patternType: 'solid',
87 | fgColor: 'ffa293'
88 | }
89 | });
90 |
91 | var indentLeft = workbook.createStyle({
92 | alignment: {
93 | indent: 1
94 | }
95 | });
96 |
97 | var centerAlign = workbook.createStyle({
98 | alignment: {
99 | horizontal: 'center'
100 | }
101 | });
102 | var bold = workbook.createStyle({
103 | font: {
104 | bold: true
105 | }
106 | });
107 | var italic = workbook.createStyle({
108 | font: {
109 | italics: true
110 | }
111 | });
112 | var redColor = workbook.createStyle({
113 | font: {
114 | color: 'FF0000'
115 | }
116 | });
117 |
118 | var rowColor = workbook.createStyle({
119 | fill: {
120 | type: 'pattern',
121 | patternType: 'solid',
122 | fgColor: 'ffffff'
123 | }
124 | });
125 |
126 | var alternateRowColor = workbook.createStyle({
127 | fill: {
128 | type: 'pattern',
129 | patternType: 'solid',
130 | fgColor: 'f2f1f3'
131 | }
132 | });
133 |
134 | module.exports = class Downloader {
135 | constructor(config, logger) {
136 | this.config = config;
137 | this.logger = logger;
138 | this.utils = new Utils();
139 | }
140 |
141 | createHeader(worksheet) {
142 |
143 | var columns = this.config.columns;
144 | var columnsKeys = Object.keys(this.config.columns);
145 |
146 | // Global sizes
147 | worksheet.row(1).setHeight(40);
148 | worksheet.row(2).setHeight(20);
149 |
150 | if (columnsKeys.indexOf('ReadOnly') > -1)
151 | worksheet.column(columnsKeys.indexOf('ReadOnly') + 1).setWidth(columns.ReadOnly);
152 | if (columnsKeys.indexOf('Mandatory') > -1)
153 | worksheet.column(columnsKeys.indexOf('Mandatory') + 1).setWidth(columns.Mandatory);
154 | if (columnsKeys.indexOf('Name') > -1)
155 | worksheet.column(columnsKeys.indexOf('Name') + 1).setWidth(columns.Name);
156 | if (columnsKeys.indexOf('Description') > -1)
157 | worksheet.column(columnsKeys.indexOf('Description') + 1).setWidth(columns.Description);
158 | if (columnsKeys.indexOf('Helptext') > -1)
159 | worksheet.column(columnsKeys.indexOf('Helptext') + 1).setWidth(columns.Helptext);
160 | if (columnsKeys.indexOf('APIName') > -1)
161 | worksheet.column(columnsKeys.indexOf('APIName') + 1).setWidth(columns.APIName);
162 | if (columnsKeys.indexOf('Type') > -1)
163 | worksheet.column(columnsKeys.indexOf('Type') + 1).setWidth(columns.Type);
164 | if (columnsKeys.indexOf('Values') > -1)
165 | worksheet.column(columnsKeys.indexOf('Values') + 1).setWidth(columns.Values);
166 |
167 | // Build header and subheader
168 | worksheet.cell(1, 1, 1, columnsKeys.length, true).string('SALESFORCE').style(global).style(header);
169 |
170 | if (columnsKeys.indexOf('ReadOnly') > -1)
171 | worksheet.cell(2, columnsKeys.indexOf('ReadOnly') + 1).string('R/O').style(global).style(subHeader).style(centerAlign);
172 | if (columnsKeys.indexOf('Mandatory') > -1)
173 | worksheet.cell(2, columnsKeys.indexOf('Mandatory') + 1).string('M').style(global).style(subHeader).style(centerAlign);
174 | if (columnsKeys.indexOf('Name') > -1)
175 | worksheet.cell(2, columnsKeys.indexOf('Name') + 1).string('Field Name').style(global).style(subHeader).style(indentLeft);
176 | if (columnsKeys.indexOf('Description') > -1)
177 | worksheet.cell(2, columnsKeys.indexOf('Description') + 1).string('Description').style(global).style(subHeader).style(indentLeft);
178 | if (columnsKeys.indexOf('Helptext') > -1)
179 | worksheet.cell(2, columnsKeys.indexOf('Helptext') + 1).string('Helptext').style(global).style(subHeader).style(indentLeft);
180 | if (columnsKeys.indexOf('APIName') > -1)
181 | worksheet.cell(2, columnsKeys.indexOf('APIName') + 1).string('API Name').style(global).style(subHeader).style(indentLeft);
182 | if (columnsKeys.indexOf('Type') > -1)
183 | worksheet.cell(2, columnsKeys.indexOf('Type') + 1).string('Type').style(global).style(subHeader).style(centerAlign);
184 | if (columnsKeys.indexOf('Values') > -1)
185 | worksheet.cell(2, columnsKeys.indexOf('Values') + 1).string('Values / Formula').style(global).style(subHeader).style(indentLeft);
186 |
187 | return 3;
188 | }
189 |
190 | mapFields(fields) {
191 | var fieldMap = {};
192 |
193 | for (var i = 0; i < fields.length; i++) {
194 | var field = fields[i];
195 | fieldMap[field.fullName] = field;
196 | }
197 |
198 | return fieldMap;
199 | }
200 |
201 | writeFields(worksheet, fields, line, validationRules) {
202 |
203 |
204 | var columns = this.config.columns;
205 | var columnsKeys = Object.keys(this.config.columns);
206 |
207 | var indexRow = 1;
208 |
209 | // Foreach field
210 | for (var j = 0; j < fields.length; j++) {
211 | var field = fields[j];
212 |
213 | if (!(this.config.hideTechFields && field.name.startsWith(this.config.techFieldPrefix))) {
214 |
215 | var isCustom = field.custom;
216 |
217 | if (!isCustom && j == 0) {
218 | worksheet.cell(line, 1, line, columnsKeys.length, true).string('Standard Fields').style(global).style(category).style(indentLeft);
219 | // Row height
220 | worksheet.row(line).setHeight(25);
221 | line++;
222 | indexRow = 1;
223 | }
224 |
225 | var rowStyle = rowColor;
226 | if (indexRow % 2 == 0) {
227 | rowStyle = alternateRowColor;
228 | }
229 |
230 |
231 | if (columnsKeys.indexOf('ReadOnly') > -1)
232 | worksheet.cell(line, columnsKeys.indexOf('ReadOnly') + 1).string(!field.updateable ? "✓" : '☐').style(global).style(centerAlign).style(rowStyle);
233 | if (columnsKeys.indexOf('Mandatory') > -1)
234 | worksheet.cell(line, columnsKeys.indexOf('Mandatory') + 1).string(!field.nillable && field.updateable && field.type != 'boolean' ? "*" : '').style(global).style(centerAlign).style(rowStyle).style(redColor);
235 | if (columnsKeys.indexOf('Name') > -1)
236 | worksheet.cell(line, columnsKeys.indexOf('Name') + 1).string(field.label != null ? field.label : field.name).style(global).style(bold).style(rowStyle).style(indentLeft);
237 | if (columnsKeys.indexOf('Description') > -1)
238 | worksheet.cell(line, columnsKeys.indexOf('Description') + 1).string(field.description != null ? field.description : '').style(global).style(rowStyle).style(indentLeft);
239 | if (columnsKeys.indexOf('Helptext') > -1)
240 | worksheet.cell(line, columnsKeys.indexOf('Helptext') + 1).string(field.inlineHelpText != null ? field.inlineHelpText : '').style(global).style(rowStyle).style(indentLeft);
241 | if (columnsKeys.indexOf('APIName') > -1)
242 | worksheet.cell(line, columnsKeys.indexOf('APIName') + 1).string(field.name).style(global).style(rowStyle).style(indentLeft);
243 |
244 | // tooling
245 | // worksheet.cell(line, columnsKeys.indexOf('APIName') + 4).string(field.LastModifiedDate != null ? field.LastModifiedDate : '').style(global).style(rowStyle).style(indentLeft);
246 |
247 | // Type property
248 | var type = this.utils.capitalize(field.type);
249 |
250 | if (type == 'Int' || type == 'Double') {
251 | type = 'Number';
252 | }
253 | if (type == 'Number' || type == 'Currency') {
254 | var precision = parseInt(field.precision);
255 | var scale = parseInt(field.scale);
256 | var finalPrecision = precision - scale;
257 |
258 | type = type + '(' + finalPrecision + ',' + field.scale + ')';
259 | }
260 |
261 | if (type == 'Boolean') {
262 | type = 'Checkbox';
263 | }
264 |
265 | if (type == 'Reference' && field.referenceTo != null) {
266 | type = 'Lookup(' + field.referenceTo + ')';
267 | }
268 | if (type == 'MasterDetail') {
269 | type = 'Master-Detail(' + field.referenceTo + ')';
270 | }
271 | if ((type == 'Text' || type == 'Textarea' || type == 'String') && field.length != null) {
272 | type = 'Text(' + field.length + ')';
273 | }
274 |
275 | if (field.calculatedFormula != null) {
276 | type = 'Formula(' + field.type + ')';
277 | }
278 |
279 | if (!field.nillable) {
280 | type += ' (Unique)';
281 | }
282 | if (field.externalId) {
283 | type += '(External ID)';
284 | }
285 |
286 | if (columnsKeys.indexOf('Type') > -1)
287 | worksheet.cell(line, columnsKeys.indexOf('Type') + 1).string(type).style(centerAlign).style(global).style(italic).style(rowStyle).style(indentLeft);
288 |
289 |
290 | // Values property
291 | var value = '';
292 |
293 | if (type == 'Picklist' || type == 'MultiselectPicklist') {
294 | if (field.globalPicklist != null) {
295 | value = 'globalPicklist(' + field.globalPicklist + ')';
296 | } else {
297 | var valuesArray = field.picklistValues;
298 | var k = 0;
299 | while (k < valuesArray.length && k < MAX_PICKLIST_VALUES) {
300 | value += valuesArray[k].value + '\n';
301 | k++;
302 | }
303 | if (valuesArray.length > MAX_PICKLIST_VALUES * 2) {
304 | value += '...\n';
305 | }
306 | if (valuesArray.length - MAX_PICKLIST_VALUES >= MAX_PICKLIST_VALUES) {
307 | k = valuesArray.length - 1
308 | while (k >= valuesArray.length - MAX_PICKLIST_VALUES) {
309 | value += valuesArray[k].value + '\n';
310 | k--;
311 | }
312 | }
313 | if (valuesArray.length > MAX_PICKLIST_VALUES * 2) {
314 | value += '(Total: ' + valuesArray.length + ' values)';
315 | }
316 | }
317 | }
318 |
319 | if (field.calculatedFormula != null) {
320 | value = field.calculatedFormula;
321 | }
322 |
323 | if (columnsKeys.indexOf('Values') > -1)
324 | worksheet.cell(line, columnsKeys.indexOf('Values') + 1).string(value).style(global).style(rowStyle).style(indentLeft);
325 |
326 | if (((!field.label.length < 24) || (!field.name.length < 24)) && !value.includes('\n'))
327 | worksheet.row(line).setHeight(25);
328 | line++;
329 | indexRow++;
330 |
331 | if (!isCustom && j + 1 < fields.length && fields[j + 1].custom) {
332 | worksheet.cell(line, 1, line, columnsKeys.length, true).string('Custom Fields').style(global).style(category).style(indentLeft);
333 | // Row height
334 | worksheet.row(line).setHeight(25);
335 | line++;
336 | indexRow = 1;
337 | }
338 | }
339 | }
340 |
341 | if (validationRules !== undefined) {
342 |
343 | worksheet.cell(line, 1, line, columnsKeys.length, true).string('Validation Rules').style(global).style(validationCategory).style(indentLeft);
344 | // Row height
345 | worksheet.row(line).setHeight(25);
346 | line++;
347 |
348 | worksheet.cell(line, 1, line, 2, true).string('Active').style(global).style(rowStyle).style(subHeader).style(centerAlign);
349 | worksheet.cell(line, 3).string('Name').style(global).style(rowStyle).style(subHeader).style(indentLeft);
350 | worksheet.cell(line, 4).string('Description').style(global).style(rowStyle).style(subHeader).style(indentLeft);
351 | worksheet.cell(line, 5).string('Error display field').style(global).style(rowStyle).style(subHeader).style(centerAlign);
352 | worksheet.cell(line, 6).string('Error message').style(global).style(rowStyle).style(subHeader).style(indentLeft);
353 | if (columnsKeys.indexOf('Helptext') > -1){
354 | worksheet.cell(line, 7, line, 8, true).string('Condition formula').style(global).style(rowStyle).style(subHeader).style(indentLeft);
355 | }else{
356 | worksheet.cell(line, 7).string('Condition formula').style(global).style(rowStyle).style(subHeader).style(indentLeft);
357 | }
358 | worksheet.row(line).setHeight(20);
359 |
360 | line++;
361 | indexRow = 1;
362 |
363 | if (Array.isArray(validationRules)) {
364 | for (var k = 0; k < validationRules.length; k++) {
365 | rowStyle = rowColor;
366 | if (indexRow % 2 == 0) {
367 | rowStyle = alternateRowColor;
368 | }
369 |
370 | worksheet.cell(line, 1, line, 2, true).string(validationRules[k].active === "true" ? "✓" : '☐').style(global).style(rowStyle).style(centerAlign);
371 | worksheet.cell(line, 3).string(validationRules[k].fullName != null ? validationRules[k].fullName : '').style(global).style(rowStyle).style(indentLeft);
372 | worksheet.cell(line, 4).string(validationRules[k].description != null ? validationRules[k].description : '').style(global).style(rowStyle).style(indentLeft);
373 | worksheet.cell(line, 5).string(validationRules[k].errorDisplayField != null ? validationRules[k].errorDisplayField : '').style(global).style(rowStyle).style(centerAlign);
374 | worksheet.cell(line, 6).string(validationRules[k].errorMessage != null ? validationRules[k].errorMessage : '').style(global).style(rowStyle).style(indentLeft);
375 | if (columnsKeys.indexOf('Helptext') > -1){
376 | worksheet.cell(line, 7, line, 8, true).string(validationRules[k].errorConditionFormula != null ? validationRules[k].errorConditionFormula : '').style(global).style(rowStyle).style(indentLeft);
377 | }else{
378 | worksheet.cell(line, 7).string(validationRules[k].errorConditionFormula != null ? validationRules[k].errorConditionFormula : '').style(global).style(rowStyle).style(indentLeft);
379 | }
380 |
381 | line++;
382 | indexRow++;
383 | }
384 | } else {
385 | rowStyle = rowColor;
386 | if (indexRow % 2 == 0) {
387 | rowStyle = alternateRowColor;
388 | }
389 |
390 | worksheet.cell(line, 1, line, 2, true).string(validationRules.active === "true" ? "✓" : '☐').style(global).style(rowStyle).style(centerAlign);
391 | worksheet.cell(line, 3).string(validationRules.fullName != null ? validationRules.fullName : '').style(global).style(rowStyle).style(indentLeft);
392 | worksheet.cell(line, 4).string(validationRules.description != null ? validationRules.description : '').style(global).style(rowStyle).style(indentLeft);
393 | worksheet.cell(line, 5).string(validationRules.errorDisplayField != null ? validationRules.errorDisplayField : '').style(global).style(rowStyle).style(centerAlign);
394 | worksheet.cell(line, 6).string(validationRules.errorMessage != null ? validationRules.errorMessage : '').style(global).style(rowStyle).style(indentLeft);
395 | worksheet.cell(line, 7).string(validationRules.errorConditionFormula != null ? validationRules.errorConditionFormula : '').style(global).style(rowStyle).style(indentLeft);
396 |
397 | line++;
398 | indexRow++;
399 | }
400 | }
401 | }
402 |
403 | generateChart(objectName, fields){
404 | var chart = '' + '\n' + '';
405 | var cpt = 0;
406 |
407 | // Foreach field
408 | for (var j = 0; j < fields.length; j++) {
409 | var field = fields[j];
410 |
411 | // Type property
412 | var type = this.utils.capitalize(field.type);
413 | var add = false;
414 | var attribute = null;
415 | var fieldLength = field.length != null ? field.length : '';
416 | var relationObject = '';
417 | var attributeKey = '';
418 | var attributeType = '';
419 |
420 | if (type == 'Reference' && field.referenceTo != null) {
421 | add = true;
422 | attributeKey = 'FOREIGN KEY';
423 | attributeType = 'LOOKUP';
424 | relationObject = field.referenceTo;
425 | }
426 | if (type == 'MasterDetail') {
427 | add = true;
428 | attributeKey = 'FOREIGN KEY';
429 | attributeType = 'MASTER DETAIL';
430 | relationObject = field.referenceTo;
431 | }
432 | if (type === 'Id'){
433 | add = true;
434 | attributeKey = 'PRIMARY KEY';
435 | attributeType = 'ID';
436 | }
437 |
438 | if(add){
439 | var fieldLabel = field.label != null ? field.label : field.name;
440 | var fieldName = field.name;
441 |
442 | if(type === 'Id'){
443 | chart += 'postgresql;ELSA;Salesforce;"' + objectName + ' (' + objectName + ')";"' + objectName + ' ID (' + fieldName + ')";' + cpt + ';"' + attributeType + '";' + fieldLength + ';"' + attributeKey + '";;' + '\n';
444 | }else{
445 | chart += 'postgresql;ELSA;Salesforce;"' + objectName + ' (' + objectName + ')";"' + fieldLabel + ' (' + fieldName + ')";' + cpt + ';"' + attributeType + '";' + fieldLength + ';"' + attributeKey + '";"Salesforce";"' + relationObject + ' (' + relationObject + ')";"' + relationObject + ' ID (Id)"' + '\n';
446 | }
447 |
448 | cpt++;
449 | }
450 |
451 | }
452 | chart += '
' + '\n' + ''
453 | return chart;
454 | }
455 |
456 | generate() {
457 | const promise = new Promise((resolve, reject) => {
458 | this.logger('Generating...');
459 |
460 | let sObjects = this.config.objects;
461 | var chart = '';
462 |
463 | for (var i = 0; i < sObjects.length; i++) {
464 | var cur = i + 1;
465 |
466 | var worksheet = workbook.addWorksheet(sObjects[i]);
467 | var line = this.createHeader(worksheet);
468 | var describePath = path.join(__dirname, FILE_DIR, '/describe/' + sObjects[i] + '.json');
469 | var metadataPath = path.join(__dirname, FILE_DIR, '/metadata/' + sObjects[i] + '.json');
470 |
471 | if (fs.existsSync(describePath)) {
472 | var currentObjectFieldsDescribe = JSON.parse(fs.readFileSync(describePath));
473 |
474 | if (fs.existsSync(metadataPath)) {
475 | var currentObjectFieldsMetadata = JSON.parse(fs.readFileSync(metadataPath));
476 | if(currentObjectFieldsMetadata.fields != null){
477 | var fieldsMap = this.mapFields(currentObjectFieldsMetadata.fields);
478 | }
479 | }
480 |
481 | for (var j = 0; j < currentObjectFieldsDescribe.length; j++) {
482 | var field = currentObjectFieldsDescribe[j];
483 | var fieldName = currentObjectFieldsDescribe[j].name;
484 |
485 | if (fieldsMap != null && fieldsMap[fieldName] != null) {
486 | var correspondingField = fieldsMap[fieldName];
487 | if (correspondingField.description != null)
488 | currentObjectFieldsDescribe[j].description = correspondingField.description;
489 |
490 | if (correspondingField.type === 'MasterDetail')
491 | currentObjectFieldsDescribe[j].type = correspondingField.type;
492 | }
493 |
494 | }
495 | }
496 |
497 | currentObjectFieldsDescribe.sort(this.utils.sortByTwoProperty('custom', 'name'));
498 |
499 | if (this.config.debug) {
500 | this.utils.log('#' + sObjects[i] + '\n#Validation RULES ' + JSON.stringify(currentObjectFieldsMetadata.validationRules), this.config);
501 | }
502 |
503 | this.writeFields(worksheet, currentObjectFieldsDescribe, line, currentObjectFieldsMetadata.validationRules);
504 | if(this.config.lucidchart)
505 | chart += this.generateChart(sObjects[i], currentObjectFieldsDescribe);
506 | }
507 |
508 | if(this.config.lucidchart){
509 | // Generate chart file (Lucidchart)
510 | this.logger('Saving lucidchart file...');
511 | const filePath = path.join(this.config.output, 'lucidchart.txt');
512 | fs.writeFileSync(filePath, chart, 'utf-8');
513 | this.logger('Lucidchart.txt file successfully saved!');
514 | }
515 |
516 | // Generate output Excel file
517 | var currentDate = new Date(Date.now());
518 | var currentDateString = currentDate.toISOString();
519 | if(this.config.outputTime){
520 | currentDateString = currentDateString.replace('T', '_').replace('Z', '').replace(/:/g,'_').replace('.','_');
521 | }else{
522 | currentDateString = currentDateString.substring(0, currentDateString.indexOf('T'));
523 | }
524 | var fileName = this.config.projectName + '_Data_Dictionary_' + currentDateString + '.xlsx'
525 | var outputFile = path.join(this.config.output, fileName);
526 | this.logger('Saving ' + fileName + '...');
527 | workbook.write(outputFile);
528 | this.logger(fileName + ' successfully saved!');
529 |
530 | resolve();
531 | });
532 | return promise;
533 | }
534 | }
535 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 |
4 | module.exports = class Utils {
5 | constructor(logger) {
6 | this.logger = logger;
7 | }
8 |
9 | log(message, config){
10 | var date = new Date();
11 | var currentDay = date.getDay();
12 | var currentMonth = date.getMonth();
13 | var currentYear = date.getFullYear();
14 | var currentHour = date.getHours();
15 | if(currentHour < 10)
16 | currentHour = '0' + currentHour.toString();
17 | var currentMinutes = date.getMinutes();
18 | if(currentMinutes < 10)
19 | currentMinutes = '0' + currentMinutes.toString();
20 | var currentSeconds = date.getSeconds();
21 | if(currentSeconds < 10)
22 | currentSeconds = '0' + currentSeconds.toString();
23 | var timestamp = '[' + currentHour + ':' + currentMinutes + ':' + currentSeconds + '] ';
24 |
25 | var logMessage = timestamp + message + '\n';
26 | if(config.debug)
27 | fs.appendFileSync(path.join(__dirname, '../files/', date.toLocaleDateString().replace(/\//g,'') + '_debug.log'), logMessage, 'utf-8');
28 | }
29 |
30 | sortByProperty(property) {
31 | return function(a, b) {
32 | var sortStatus = 0;
33 | if (a[property] < b[property]) {
34 | sortStatus = -1;
35 | } else if (a[property] > b[property]) {
36 | sortStatus = 1;
37 | }
38 |
39 | return sortStatus;
40 | };
41 | }
42 |
43 | sortByTwoProperty(prop1, prop2) {
44 | 'use strict';
45 | return function(a, b) {
46 | if (a[prop1] === undefined) {
47 | return 1;
48 | } else if (b[prop1] === undefined) {
49 | return -1;
50 | } else if (a[prop1] === b[prop1]) {
51 | var sortStatus = 0;
52 | if (a[prop2].toString().toLowerCase() < b[prop2].toString().toLowerCase()) {
53 | sortStatus = -1;
54 | } else if (String(a[prop2]).toString().toLowerCase() > b[prop2].toString().toLowerCase()) {
55 | sortStatus = 1;
56 | }
57 | } else {
58 | if (a[prop1].toString().toLowerCase() < b[prop1].toString().toLowerCase()) {
59 | sortStatus = -1;
60 | } else {
61 | sortStatus = 1;
62 | }
63 | };
64 | return sortStatus;
65 | };
66 | }
67 |
68 | formatInt (number) {
69 | let str = number.toLocaleString('en-US');
70 | str = str.replace(/,/g, ' ');
71 | str = str.replace(/\./, ',');
72 | return str;
73 | }
74 |
75 | rmDir (dirPath, extension, removeSelf) {
76 | if (removeSelf === undefined)
77 | removeSelf = true;
78 | try {
79 | var files = fs.readdirSync(dirPath);
80 | } catch (e) /* istanbul ignore next */ {
81 | return false;
82 | }
83 |
84 | if (files.length > 0)
85 | for (let i = 0; i < files.length; i++) {
86 | let filePath = dirPath + '/' + files[i];
87 | /* istanbul ignore else */
88 | if (fs.statSync(filePath).isFile()) {
89 | if (extension !== null) {
90 | if (path.extname(filePath) == extension)
91 | fs.unlinkSync(filePath);
92 | } else {
93 | fs.unlinkSync(filePath);
94 | }
95 |
96 | } else {
97 | utils.rmDir(filePath);
98 | }
99 | }
100 | /* istanbul ignore else */
101 | if (removeSelf)
102 | fs.rmdirSync(dirPath);
103 |
104 | return true;
105 | };
106 |
107 | capitalize (string) {
108 | return string.charAt(0).toUpperCase() + string.slice(1);
109 | }
110 | };
111 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sfdc-generate-data-dictionary",
3 | "version": "1.2.15",
4 | "description": "Generate data dictionary from a Salesforce Org",
5 | "main": "index.js",
6 | "bin": {
7 | "sgd": "./bin/cli"
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git://github.com/gavignon/sfdc-generate-data-dictionary.git"
15 | },
16 | "keywords": [
17 | "salesforce",
18 | "data-dictionary"
19 | ],
20 | "author": "Gil Avignon ",
21 | "license": "MIT",
22 | "dependencies": {
23 | "bytes": "^2.5.0",
24 | "commander": "^2.11.0",
25 | "excel4node": "^1.2.1",
26 | "jsforce": "^1.8.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------