Skip to content

Commit 7515775

Browse files
committed
Update Electron, Fix changeset, set rollbackOnError
1 parent fbaa590 commit 7515775

File tree

11 files changed

+523
-407
lines changed

11 files changed

+523
-407
lines changed

package-lock.json

Lines changed: 152 additions & 297 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"archiver": "^2.1.1",
99
"async": "^2.6.1",
1010
"decompress-zip": "^0.3.1",
11+
"electron": "^2.0.9",
1112
"electron-squirrel-startup": "^1.0.0",
1213
"events": "^3.0.0",
1314
"github-api": "^3.0.0",
@@ -50,7 +51,6 @@
5051
},
5152
"devDependencies": {
5253
"app-builder-lib": "^20.27.0",
53-
"electron": "^2.0.6",
5454
"electron-builder": "^20.26.1",
5555
"electron-builder-squirrel-windows": "^20.27.0",
5656
"electron-packager": "^12.0.2",

src/class/Connect.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ class Connect {
337337
ev.sender.send('sfdc-metadata-list-callback',utils.serialize(err), result);
338338
}
339339
try{
340+
let objLabelMap = {};
340341
let metadataList = [];
341342
let folders = [];
342343
const connection = arg.connection;
@@ -347,6 +348,12 @@ class Connect {
347348
// Refresh Token for bitbucket
348349
self.restoreToken(connection, token);
349350
}
351+
return sfdcApi.describeGlobal();
352+
})
353+
.then(function(objects) {
354+
for(let obj of objects) {
355+
objLabelMap[obj.name] = obj.label;
356+
}
350357
return sfdcApi.describeMetadata();
351358
})
352359
.then(function(result) {
@@ -355,7 +362,7 @@ class Connect {
355362
})
356363
.then(function(result) {
357364
folders = result;
358-
return sfdcApi.getMetadataDetailList(metadataList, folders);
365+
return sfdcApi.getMetadataDetailList(metadataList, folders, objLabelMap);
359366
})
360367
.then(function(components) {
361368
// Set language label

src/class/Pipeline.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,8 @@ class Pipeline {
271271
})
272272
.then(function(zipPath) {
273273
// Do Deploy
274-
return metadata.deploy(toConn, zipPath, {}, function(deployResult) {
274+
// opt @see https://jsforce.github.io/jsforce/doc/Metadata.html#deploy
275+
return metadata.deploy(toConn, zipPath, { rollbackOnError : true }, function(deployResult) {
275276
self.outputDeployProcessLog(pipelineLog, deployResult);
276277
});
277278
})

src/class/SfdcApi.js

Lines changed: 220 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,17 @@ class SfdcApi {
153153
}, 5000);
154154
});
155155
}
156+
157+
describeGlobal() {
158+
const self = this;
159+
return new Promise(function(resolve, reject) {
160+
self.conn.describeGlobal(function(err, res) {
161+
if(err) { return reject(err); }
162+
return resolve(res.sobjects);
163+
});
164+
});
165+
166+
}
156167

157168
describeMetadata() {
158169
const self = this;
@@ -192,6 +203,7 @@ class SfdcApi {
192203
self.getMetadataList(type)
193204
.then(function(metadata) {
194205
for(let meta of metadata) {
206+
if(meta.manageableState !== 'unmanaged' && meta.manageableState !== undefined) continue;
195207
folders.push(meta);
196208
}
197209
return completion(null);
@@ -201,12 +213,26 @@ class SfdcApi {
201213
});
202214
}, function(err){
203215
if(err) return reject(err);
204-
return resolve(folders);
216+
// Set Folder Name
217+
self.conn.query('SELECT Id, Name FROM Folder', function(err, res) {
218+
if(err) return reject(err);
219+
let labelMap = {};
220+
for(let i = 0; i < res.records.length; i++) {
221+
const record = res.records[i];
222+
labelMap[record.Id] = record.Name;
223+
}
224+
for(let i = 0; i < folders.length; i++) {
225+
if(labelMap.hasOwnProperty(folders[i].id)) {
226+
folders[i]['label'] = labelMap[folders[i].id];
227+
}
228+
}
229+
return resolve(folders);
230+
});
205231
}); // .async.eachSeries
206232
});
207233
}
208234

209-
getMetadataDetailList(metadataList, folders) {
235+
getMetadataDetailList(metadataList, folders, objLabelMap) {
210236
const self = this;
211237
//const excepts = ['LightningComponentBundle'];
212238
return new Promise(function(resolve, reject) {
@@ -227,32 +253,43 @@ class SfdcApi {
227253
let xmlName = (meta.xmlName == 'EmailTemplate') ? 'EmailFolder' : meta.xmlName + 'Folder';
228254
for(let fd of folders) {
229255
if(fd.type !== xmlName) continue;
230-
queues.push({type: meta.xmlName, folder: fd.fullName});
256+
// attribute folderLabel is used for set folder name to metadata in filterMetadata()
257+
queues.push({type: meta.xmlName, folder: fd.fullName, folderLabel : fd.label});
231258
}
232259
}
233260
let metadataDetailMap = {};
234261
// console.log('>>>> queues', queues);
235262
async.eachLimit(queues, 5, function(queue, completion) {
236-
//console.log('>>>> queue', queue);
237-
self.getMetadataList(queue)
263+
// Layout metadata type does not accept folder attribute
264+
const param = (queue.type == 'Layout') ? { type : queue.type } : { type : queue.type, folder : queue.folder };
265+
self.getMetadataList(param)
238266
.then(function(metadata) {
239-
//console.log('>>>> queue : ' + queue.type, metadata.length);
267+
return self.filterMetadata(queue, metadata, objLabelMap);
268+
})
269+
.then(function(metadata) {
270+
if(metadataDetailMap[queue.type] && metadataDetailMap[queue.type].length > 0) {
271+
// e.g. multiple dashboard folders
272+
metadataDetailMap[queue.type].push(...metadata);
273+
} else if(queue.type !== 'Workflow') {
274+
// Workflow needs be filtered
275+
metadataDetailMap[queue.type] = metadata;
276+
}
277+
// clear standard object (custom field of standard object need to be pulled)
240278
if(queue.type == 'CustomObject') {
241-
let customObjects = [];
242-
for(let meta of metadata) {
243-
if(!meta.fullName.endsWith('__c')) {
244-
// Filter standard object
279+
let customObjs = [];
280+
for(let i = 0; i < metadata.length; i++) {
281+
const meta = metadata[i];
282+
if(!meta.fullName.endsWith('__c') && !meta.fullName.endsWith('__mdt') && !meta.fullName.endsWith('__kav')) {
245283
continue;
246284
}
247-
customObjects.push(meta);
285+
customObjs.push(meta);
248286
}
249-
metadataDetailMap[queue.type] = customObjects;
250-
} else {
251-
metadataDetailMap[queue.type] = metadata;
287+
metadataDetailMap[queue.type] = customObjs;
252288
}
289+
253290
if(queue.type == 'CustomObject' || queue.type == 'Workflow') {
254291
//TODO SharingRules
255-
return self.readChildMetadata(metadata);
292+
return self.readChildMetadata(metadata, objLabelMap);
256293
} else {
257294
return true;
258295
}
@@ -277,7 +314,149 @@ class SfdcApi {
277314
});
278315
}
279316

280-
readChildMetadata(metadata) {
317+
// Filter unnecessary metadata, e.g : standard application
318+
// Set metadata label with SOAP api query
319+
filterMetadata(queue, metadata, objLabelMap) {
320+
const self = this;
321+
const type = queue.type;
322+
return new Promise(function(resolve, reject) {
323+
let filterFunction = function(meta) {
324+
// escape standard and package metadata
325+
return meta.manageableState !== 'unmanaged' && meta.manageableState !== undefined;
326+
};
327+
let resetFunction;
328+
switch(type){
329+
case 'ApexClass' :
330+
case 'ApexComponent' :
331+
case 'ApexPage' :
332+
case 'ApexTrigger' :
333+
case 'CustomApplication' :
334+
case 'CustomLabel' :
335+
case 'CustomField' :
336+
case 'CustomTab' :
337+
case 'Dashboard' :
338+
case 'Document' :
339+
case 'EmailTemplate' :
340+
case 'ListView' :
341+
case 'ReportType' :
342+
case 'Report' :
343+
filterFunction = function(meta) {
344+
// escape standard and package metadata
345+
return (utils.isNotBlank(meta.namespacePrefix));
346+
};
347+
if(type == 'CustomTab') {
348+
resetFunction = function(meta) {
349+
meta['label'] = (objLabelMap.hasOwnProperty(meta.fullName)) ? objLabelMap[meta.fullName] : meta.fullName;
350+
return meta;
351+
};
352+
}
353+
if(type == 'Dashboard' || type == 'Document' || type == 'EmailTemplate' || type == 'Report') {
354+
resetFunction = function(meta) {
355+
const names = meta.fullName.split('/');
356+
meta['customName'] = names[(names.length-1)];
357+
meta['folder'] = queue.folder;
358+
meta['folderLabel'] = queue.folderLabel;
359+
return meta;
360+
};
361+
}
362+
break;
363+
case 'CustomMetadata' :
364+
case 'QuickAction' :
365+
// Case.Reply → Reply , Case
366+
resetFunction = function(meta) {
367+
const names = meta.fullName.split('.');
368+
if(names.length != 2) return meta;
369+
const objName = (type == 'CustomMetadata') ? (names[0] + '__mdt') : names[0];
370+
meta['object'] = objName;
371+
meta['objectLabel'] = (objLabelMap.hasOwnProperty(objName)) ? objLabelMap[objName] : objName;
372+
meta['customName'] = names[1]; // Case.Reply → Reply
373+
meta['label'] = names[1];
374+
return meta;
375+
};
376+
break;
377+
case 'CustomObject' :
378+
case 'MatchingRules' :
379+
/*filterFunction = function(meta) {
380+
return (!meta.fullName.endsWith('__c') && !meta.fullName.endsWith('__mdt') && !meta.fullName.endsWith('__kav'));
381+
}*/
382+
resetFunction = function(meta) {
383+
meta['label'] = (objLabelMap.hasOwnProperty(meta.fullName)) ? objLabelMap[meta.fullName] : meta.fullName;
384+
return meta;
385+
};
386+
break;
387+
case 'Layout' :
388+
// e.g. OpportunityLineItem-商談商品 ページレイアウト → 商談商品 ページレイアウト
389+
resetFunction = function(meta) {
390+
const names = meta.fullName.split('-');
391+
if(names.length > 1) {
392+
const objName = names[0];
393+
meta['object'] = objName;
394+
meta['objectLabel'] = (objLabelMap.hasOwnProperty(objName)) ? objLabelMap[objName] : objName;
395+
names.shift(); // remove object name
396+
meta['customName'] = names.join('-');
397+
}
398+
return meta;
399+
};
400+
break;
401+
case 'Group' :
402+
case 'RecordType' :
403+
case 'Role' :
404+
// Needs requestMetaLabel
405+
break;
406+
default :
407+
break;
408+
}
409+
410+
let targets = [];
411+
for(let meta of metadata) {
412+
if(filterFunction && filterFunction(meta)) {
413+
// Filter standard object
414+
continue;
415+
}
416+
if(resetFunction) {
417+
meta = resetFunction(meta);
418+
}
419+
targets.push(meta);
420+
}
421+
self.requestMetaLabel(type, targets, resolve);
422+
});
423+
}
424+
425+
// Request metadata label, e.g. : dashboard
426+
requestMetaLabel(type, targets, callback) {
427+
const self = this;
428+
const labelQueryMap = {
429+
'Group' : { 'field' : 'Name', 'query' : 'SELECT Id, Name FROM Group' },
430+
'Document' : { 'field' : 'Name', 'query' : 'SELECT Id, Name FROM Document' },
431+
'Dashboard' : { 'field' : 'Title', 'query' : 'SELECT Id, Title FROM Dashboard' },
432+
'EmailTemplate' : { 'field' : 'Name', 'query' : 'SELECT Id, Name FROM EmailTemplate' },
433+
'ListView' : { 'field' : 'Name', 'query' : 'SELECT Id, Name FROM ListView' },
434+
'RecordType' : { 'field' : 'Name', 'query' : 'SELECT Id, Name FROM RecordType' },
435+
'Report' : { 'field' : 'Name', 'query' : 'SELECT Id, Name FROM Report' },
436+
'Role' : { 'field' : 'Name', 'query' : 'SELECT Id, Name FROM UserRole' }
437+
}
438+
if(labelQueryMap.hasOwnProperty(type)) {
439+
var labelQuery = labelQueryMap[type];
440+
self.conn.query(labelQuery.query, function(err, res) {
441+
if(err) return reject(err);
442+
let labelMap = {};
443+
for(let i = 0; i < res.records.length; i++) {
444+
const record = res.records[i];
445+
labelMap[record.Id] = record[labelQuery.field];
446+
}
447+
for(let i = 0; i < targets.length; i++) {
448+
if(labelMap.hasOwnProperty(targets[i].id)) {
449+
targets[i]['label'] = labelMap[targets[i].id];
450+
}
451+
}
452+
return callback(targets);
453+
});
454+
} else {
455+
return callback(targets);
456+
}
457+
}
458+
459+
readChildMetadata(metadata, objLabelMap) {
281460
const self = this;
282461
return new Promise(function(resolve, reject) {
283462
if(metadata.length == 0) {
@@ -345,8 +524,15 @@ class SfdcApi {
345524
// Filter standard field
346525
continue;
347526
}
348-
if(meta.fullName) cMeta['Object'] = meta.fullName;
349-
if(meta.label) cMeta['ObjectLabel'] = meta.label;
527+
if(meta.fullName) cMeta['object'] = meta.fullName;
528+
if(meta.label) cMeta['objectLabel'] = meta.label;
529+
if(utils.isBlank(cMeta.ObjectLabel) && objLabelMap.hasOwnProperty(meta.fullName)) {
530+
// Set Object Label for Workflow Alert
531+
cMeta['objectLabel'] = objLabelMap[meta.fullName];
532+
}
533+
if(child.typeName == 'WorkflowAlert' && cMeta.description) cMeta['label'] = cMeta.description;
534+
if(child.typeName == 'WorkflowFieldUpdate' && cMeta.name) cMeta['customName'] = cMeta.name;
535+
350536
metadataMap[child.typeName].push(cMeta);
351537
}
352538
}
@@ -423,42 +609,35 @@ class SfdcApi {
423609
types: pipeline.targetTypes
424610
}
425611
});
426-
const Raven = require('raven');
427612
retrieveResult.complete(function(err, result) {
428613
if(err) return reject(err);
429-
if(result.success=='true') {
430-
self.logger('[SFDC] Retrieve metadata Done.');
431-
// self.logger('[SFDC] packagePath DecompressZip .' + packagePath + ' > ' + metaPath + ' > ' + (fs.statSync(packagePath).size));
432-
if(!fs.existsSync(packagePath)) return reject(new Error('Metadata zip not found'));
433-
self.extractMetaZip(packagePath, metaPath, 0, function(err, success) {
434-
if(err) return reject(err);
435-
return resolve(success);
436-
});
437-
} else {
614+
if(result.success!='true') {
438615
return reject(new Error('Retrieve metadata failed'));
439616
}
440617
});
618+
zipstream.on('error', function(err){
619+
return reject(err);
620+
});
621+
// Retrieve and zip done
622+
zipstream.on('close', function(){
623+
self.logger('[SFDC] Retrieve metadata Done.');
624+
// self.logger('[SFDC] packagePath DecompressZip .' + packagePath + ' > ' + metaPath + ' > ' + (fs.statSync(packagePath).size));
625+
if(!fs.existsSync(packagePath)) return reject(new Error('Metadata zip not found'));
626+
self.extractMetaZip(packagePath, metaPath, function(err, success) {
627+
if(err) return reject(err);
628+
return resolve(success);
629+
});
630+
});
441631
retrieveResult.stream().pipe(zipstream);
442632
});
443633
}
444634

445635
// Extract metadata zip file to src folder
446-
// Metadata api may zip file cost sevaral ms
447-
extractMetaZip(sourceZipPath, targetPath, presize, callback) {
448-
const self = this;
636+
extractMetaZip(sourceZipPath, targetPath, callback) {
449637
const fileinfo = fs.statSync(sourceZipPath);
450638
if(fileinfo.size == 0) {
451639
// Is written
452-
return setTimeout(function(){
453-
self.extractMetaZip(sourceZipPath, targetPath, presize, callback)
454-
}, 500);
455-
}
456-
if(presize !== fileinfo.size) {
457-
// Maybe written
458-
presize = fileinfo.size;
459-
return setTimeout(function(){
460-
self.extractMetaZip(sourceZipPath, targetPath, presize, callback)
461-
}, 500);
640+
return callback(new Error('Metadata zip not found'));
462641
}
463642
// Ready to extract
464643
const unzipper = new DecompressZip(sourceZipPath)

0 commit comments

Comments
 (0)