testem 3.19.0 → 3.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,10 +8,9 @@ This file more or less figures out how to launch any browser on any platform.
8
8
  */
9
9
 
10
10
 
11
- var Bluebird = require('bluebird');
12
-
13
11
  var fileutils = require('./utils/fileutils');
14
12
  var envWithLocalPath = require('./utils/env-with-local-path');
13
+ const { filter, asCallback } = require('./utils/promises');
15
14
 
16
15
  var executableExists = function(exe, config) {
17
16
  return fileutils.executableExists(exe, { env: envWithLocalPath(config) });
@@ -24,7 +23,7 @@ function getAvailableBrowsers(config, browsers, cb) {
24
23
  b.protocol = 'browser';
25
24
  });
26
25
 
27
- return Bluebird.filter(browsers, function(browser) {
26
+ return filter(browsers, function(browser) {
28
27
  return isInstalled(browser, config).then(function(result) {
29
28
  if (!result) {
30
29
  return false;
@@ -33,7 +32,7 @@ function getAvailableBrowsers(config, browsers, cb) {
33
32
  browser.exe = result;
34
33
  return true;
35
34
  });
36
- }).asCallback(cb);
35
+ }).then(...asCallback(cb));
37
36
  }
38
37
 
39
38
  function isInstalled(browser, config) {
@@ -50,11 +49,11 @@ function isInstalled(browser, config) {
50
49
 
51
50
  function checkBrowser(browser, property, method) {
52
51
  if (!browser[property]) {
53
- return Bluebird.resolve(false);
52
+ return Promise.resolve(false);
54
53
  }
55
54
 
56
55
  if (Array.isArray(browser[property])) {
57
- return Bluebird.filter(browser[property], method).then(function(result) {
56
+ return filter(browser[property], method).then(function(result) {
58
57
  if (result.length === 0) {
59
58
  return false;
60
59
  }
package/lib/config.js CHANGED
@@ -8,16 +8,14 @@ or `testem.json` config file.
8
8
 
9
9
  */
10
10
 
11
-
12
11
  const os = require('os');
13
12
  const fs = require('fs');
14
13
  const yaml = require('js-yaml');
15
- const log = require('npmlog');
14
+ const log = require('./log');
16
15
  const path = require('path');
17
- const glob = require('glob').glob;
18
16
  const url = require('url');
19
17
  const querystring = require('querystring');
20
- const Bluebird = require('bluebird');
18
+ const { filter, reduce } = require('./utils/promises');
21
19
 
22
20
  const browser_launcher = require('./browser_launcher');
23
21
  const LauncherFactory = require('./launcher-factory');
@@ -29,7 +27,7 @@ const _ = require('lodash');
29
27
 
30
28
  const knownBrowsers = require('./utils/known-browsers');
31
29
  const { convertToPosix } = require('./utils/posix');
32
- const globAsync = glob;
30
+ const expandGlobPattern = require('./utils/expand_glob_pattern');
33
31
 
34
32
  class Config {
35
33
  constructor(appMode, progOptions, config) {
@@ -39,7 +37,7 @@ class Config {
39
37
  this.fileOptions = {};
40
38
  this.config = config || {};
41
39
  this.getters = {
42
- test_page: 'getTestPage'
40
+ test_page: 'getTestPage',
43
41
  };
44
42
 
45
43
  if (appMode === 'dev') {
@@ -71,12 +69,31 @@ class Config {
71
69
 
72
70
  // Try all testem.json, testem.yml and testem.js
73
71
  // testem.json gets precedence
74
- let files = ['testem.json', '.testem.json', '.testem.yml', 'testem.yml', 'testem.js', '.testem.js', 'testem.cjs', '.testem.cjs'];
75
- return Bluebird.filter(files.map(this.resolveConfigPath.bind(this)), fileExists).then(matched => {
72
+ let files = [
73
+ 'testem.json',
74
+ '.testem.json',
75
+ '.testem.yml',
76
+ 'testem.yml',
77
+ 'testem.js',
78
+ '.testem.js',
79
+ 'testem.cjs',
80
+ '.testem.cjs',
81
+ ];
82
+ return filter(
83
+ files.map(this.resolveConfigPath.bind(this)),
84
+ fileExists,
85
+ ).then((matched) => {
76
86
  let configFile = matched[0];
77
87
  if (matched.length > 1) {
78
- let baseNames = matched.map(fileName => path.basename(fileName));
79
- console.warn('Found ' + matched.length + ' config files (' + baseNames + '), using ' + baseNames[0]);
88
+ let baseNames = matched.map((fileName) => path.basename(fileName));
89
+ console.warn(
90
+ 'Found ' +
91
+ matched.length +
92
+ ' config files (' +
93
+ baseNames +
94
+ '), using ' +
95
+ baseNames[0],
96
+ );
80
97
  }
81
98
  if (configFile) {
82
99
  this.readConfigFile(configFile, callback);
@@ -99,7 +116,7 @@ class Config {
99
116
 
100
117
  client() {
101
118
  return {
102
- decycle_depth: this.get('client_decycle_depth')
119
+ decycle_depth: this.get('client_decycle_depth'),
103
120
  };
104
121
  }
105
122
 
@@ -122,7 +139,8 @@ class Config {
122
139
  }
123
140
 
124
141
  readConfigFile(configFile, callback) {
125
- if (!configFile) { // allow empty configFile for programmatic setups
142
+ if (!configFile) {
143
+ // allow empty configFile for programmatic setups
126
144
  if (callback) {
127
145
  callback.call(this);
128
146
  }
@@ -143,7 +161,7 @@ class Config {
143
161
  readJS(configFile, callback) {
144
162
  let exportedConfig = require(this.resolveConfigPath(configFile));
145
163
  if (typeof exportedConfig === 'function') {
146
- Promise.resolve(exportedConfig()).then(obj => {
164
+ Promise.resolve(exportedConfig()).then((obj) => {
147
165
  this.fileOptions = obj;
148
166
  if (callback) {
149
167
  callback.call(this);
@@ -158,28 +176,36 @@ class Config {
158
176
  }
159
177
 
160
178
  readYAML(configFile, callback) {
161
- fs.readFile(configFile, (err, data) => {
162
- if (!err) {
163
- let cfg = yaml.load(String(data));
164
- this.fileOptions = cfg;
165
- }
166
- if (callback) {
167
- callback.call(this);
168
- }
169
- });
179
+ return fs.promises
180
+ .readFile(configFile)
181
+ .then(
182
+ (data) => {
183
+ this.fileOptions = yaml.load(String(data));
184
+ },
185
+ () => {},
186
+ )
187
+ .then(() => {
188
+ if (callback) {
189
+ callback.call(this);
190
+ }
191
+ });
170
192
  }
171
193
 
172
194
  readJSON(configFile, callback) {
173
- fs.readFile(configFile, (err, data) => {
174
- if (!err) {
175
- let cfg = JSON.parse(data.toString());
176
- this.fileOptions = cfg;
177
- this.progOptions.file = configFile;
178
- }
179
- if (callback) {
180
- callback.call(this);
181
- }
182
- });
195
+ return fs.promises
196
+ .readFile(configFile)
197
+ .then(
198
+ (data) => {
199
+ this.fileOptions = JSON.parse(data.toString());
200
+ this.progOptions.file = configFile;
201
+ },
202
+ () => {},
203
+ )
204
+ .then(() => {
205
+ if (callback) {
206
+ callback.call(this);
207
+ }
208
+ });
183
209
  }
184
210
 
185
211
  mergeUrlAndQueryParams(urlString, queryParamsObj) {
@@ -196,11 +222,12 @@ class Config {
196
222
 
197
223
  let urlObj = url.parse(urlString);
198
224
  let outputQueryParams = querystring.parse(urlObj.query) || {};
199
- Object.keys(queryParamsObj).forEach(param => {
225
+ Object.keys(queryParamsObj).forEach((param) => {
200
226
  outputQueryParams[param] = queryParamsObj[param];
201
227
  });
202
228
  urlObj.query = outputQueryParams;
203
- urlObj.search = querystring.stringify(outputQueryParams)
229
+ urlObj.search = querystring
230
+ .stringify(outputQueryParams)
204
231
  .replace(/=&/g, '&')
205
232
  .replace(/=$/, '');
206
233
  urlObj.path = urlObj.pathname + urlObj.search;
@@ -215,20 +242,32 @@ class Config {
215
242
  testPage = [testPage];
216
243
  }
217
244
 
218
- return testPage.map(page => this.mergeUrlAndQueryParams(page, queryParams));
245
+ return testPage.map((page) =>
246
+ this.mergeUrlAndQueryParams(page, queryParams),
247
+ );
219
248
  }
220
249
 
221
250
  getConfigProperty(key) {
222
251
  if (this.config && key in this.config) {
223
252
  return this.config[key];
224
253
  }
225
- if (key in this.progOptions && typeof this.progOptions[key] !== 'undefined') {
254
+ if (
255
+ key in this.progOptions &&
256
+ typeof this.progOptions[key] !== 'undefined'
257
+ ) {
226
258
  return this.progOptions[key];
227
259
  }
228
- if (key in this.fileOptions && typeof this.fileOptions[key] !== 'undefined') {
260
+ if (
261
+ key in this.fileOptions &&
262
+ typeof this.fileOptions[key] !== 'undefined'
263
+ ) {
229
264
  return this.fileOptions[key];
230
265
  }
231
- if (this.defaultOptions && key in this.defaultOptions && typeof this.defaultOptions[key] !== 'undefined') {
266
+ if (
267
+ this.defaultOptions &&
268
+ key in this.defaultOptions &&
269
+ typeof this.defaultOptions[key] !== 'undefined'
270
+ ) {
232
271
  return this.defaultOptions[key];
233
272
  }
234
273
  if (key in this.defaults) {
@@ -264,27 +303,35 @@ class Config {
264
303
 
265
304
  getAvailableLaunchers(cb) {
266
305
  let browsers = knownBrowsers(process.platform, this);
267
- browser_launcher.getAvailableBrowsers(this, browsers, (err, availableBrowsers) => {
268
- if (err) {
269
- return cb(err);
270
- }
306
+ browser_launcher.getAvailableBrowsers(
307
+ this,
308
+ browsers,
309
+ (err, availableBrowsers) => {
310
+ if (err) {
311
+ return cb(err);
312
+ }
271
313
 
272
- let availableLaunchers = {};
273
- availableBrowsers.forEach(browser => {
274
- let newLauncher = new LauncherFactory(browser.name, browser, this);
275
- availableLaunchers[browser.name.toLowerCase()] = newLauncher;
276
- });
314
+ let availableLaunchers = {};
315
+ availableBrowsers.forEach((browser) => {
316
+ let newLauncher = new LauncherFactory(browser.name, browser, this);
317
+ availableLaunchers[browser.name.toLowerCase()] = newLauncher;
318
+ });
277
319
 
278
- // add custom launchers
279
- let customLaunchers = this.get('launchers');
280
- if (customLaunchers) {
281
- for (let name in customLaunchers) {
282
- let newLauncher = new LauncherFactory(name, customLaunchers[name], this);
283
- availableLaunchers[name.toLowerCase()] = newLauncher;
320
+ // add custom launchers
321
+ let customLaunchers = this.get('launchers');
322
+ if (customLaunchers) {
323
+ for (let name in customLaunchers) {
324
+ let newLauncher = new LauncherFactory(
325
+ name,
326
+ customLaunchers[name],
327
+ this,
328
+ );
329
+ availableLaunchers[name.toLowerCase()] = newLauncher;
330
+ }
284
331
  }
285
- }
286
- cb(null, availableLaunchers);
287
- });
332
+ cb(null, availableLaunchers);
333
+ },
334
+ );
288
335
  }
289
336
 
290
337
  getLaunchers(cb) {
@@ -310,7 +357,7 @@ class Config {
310
357
  let skip = this.get('skip');
311
358
  if (skip) {
312
359
  skip = skip.toLowerCase().split(',');
313
- launchers = launchers.filter(name => skip.indexOf(name) === -1);
360
+ launchers = launchers.filter((name) => skip.indexOf(name) === -1);
314
361
  }
315
362
  return launchers;
316
363
  }
@@ -320,7 +367,7 @@ class Config {
320
367
  let wanted = this.getWantedLauncherNames(available);
321
368
  let err = null;
322
369
 
323
- wanted.forEach(name => {
370
+ wanted.forEach((name) => {
324
371
  let launcher = available[name.toLowerCase()];
325
372
  if (!launcher) {
326
373
  if (this.appMode === 'dev' || this.get('ignore_missing_launchers')) {
@@ -328,10 +375,12 @@ class Config {
328
375
  } else {
329
376
  err = new Error(
330
377
  'Launcher ' +
331
- name +
332
- ' not found. Not installed? Available: ' +
333
- Object.keys(available).map(elm => `"${elm}"`).join(', ') +
334
- '.'
378
+ name +
379
+ ' not found. Not installed? Available: ' +
380
+ Object.keys(available)
381
+ .map((elm) => `"${elm}"`)
382
+ .join(', ') +
383
+ '.',
335
384
  );
336
385
  }
337
386
  } else {
@@ -343,38 +392,62 @@ class Config {
343
392
 
344
393
  printLauncherInfo() {
345
394
  this.getAvailableLaunchers((err, launchers) => {
346
- let launch_in_dev = (this.get('launch_in_dev') || [])
347
- .map(s => s.toLowerCase());
395
+ let launch_in_dev = (this.get('launch_in_dev') || []).map((s) =>
396
+ s.toLowerCase(),
397
+ );
348
398
  let launch_in_ci = this.get('launch_in_ci');
349
399
  if (launch_in_ci) {
350
- launch_in_ci = launch_in_ci.map(s => s.toLowerCase());
400
+ launch_in_ci = launch_in_ci.map((s) => s.toLowerCase());
351
401
  }
352
- launchers = Object.keys(launchers).map(k => launchers[k]);
402
+ launchers = Object.keys(launchers).map((k) => launchers[k]);
353
403
 
354
- const launcherColumnWidth = launchers.reduce((acc, current) => acc > current.name.length ? acc : current.name.length, 0);
404
+ const launcherColumnWidth = launchers.reduce(
405
+ (acc, current) =>
406
+ acc > current.name.length ? acc : current.name.length,
407
+ 0,
408
+ );
355
409
  const launcherTitle = 'Launcher'.padEnd(launcherColumnWidth, ' ');
356
410
  const launcherTitleDivider = ''.padEnd(launcherColumnWidth, '-');
357
411
 
358
- console.log('Have ' + launchers.length + ' launchers available; auto-launch info displayed on the right.');
412
+ console.log(
413
+ 'Have ' +
414
+ launchers.length +
415
+ ' launchers available; auto-launch info displayed on the right.',
416
+ );
359
417
  console.log(); // newline
360
418
  console.log(`${launcherTitle} Type CI Dev`);
361
419
  console.log(`${launcherTitleDivider} ------------ -- ---`);
362
- console.log(launchers.map(launcher => {
363
- let protocol = launcher.settings.protocol;
364
- let kind = protocol === 'browser' ?
365
- 'browser' : (
366
- protocol === 'tap' ?
367
- 'process(TAP)' : 'process');
368
- let dev = launch_in_dev.indexOf(launcher.name.toLowerCase()) !== -1 ?
369
- Chars.mark :
370
- ' ';
371
- let ci = !launch_in_ci || launch_in_ci.indexOf(launcher.name.toLowerCase()) !== -1 ?
372
- Chars.mark :
373
- ' ';
374
- return (pad(launcher.name, launcherColumnWidth + 2, ' ', 1) +
375
- pad(kind, 12, ' ', 1) +
376
- ' ' + ci + ' ' + dev + ' ');
377
- }).join('\n'));
420
+ console.log(
421
+ launchers
422
+ .map((launcher) => {
423
+ let protocol = launcher.settings.protocol;
424
+ let kind =
425
+ protocol === 'browser'
426
+ ? 'browser'
427
+ : protocol === 'tap'
428
+ ? 'process(TAP)'
429
+ : 'process';
430
+ let dev =
431
+ launch_in_dev.indexOf(launcher.name.toLowerCase()) !== -1
432
+ ? Chars.mark
433
+ : ' ';
434
+ let ci =
435
+ !launch_in_ci ||
436
+ launch_in_ci.indexOf(launcher.name.toLowerCase()) !== -1
437
+ ? Chars.mark
438
+ : ' ';
439
+ return (
440
+ pad(launcher.name, launcherColumnWidth + 2, ' ', 1) +
441
+ pad(kind, 12, ' ', 1) +
442
+ ' ' +
443
+ ci +
444
+ ' ' +
445
+ dev +
446
+ ' '
447
+ );
448
+ })
449
+ .join('\n'),
450
+ );
378
451
  });
379
452
  }
380
453
 
@@ -389,7 +462,7 @@ class Config {
389
462
  // Filter glob < 6 negation patterns to still support them
390
463
  // See https://github.com/isaacs/node-glob/tree/3f883c43#comments-and-negation
391
464
  let positiveWants = [];
392
- want.forEach(patternEntry => {
465
+ want.forEach((patternEntry) => {
393
466
  let pattern = isa(patternEntry, String) ? patternEntry : patternEntry.src;
394
467
  if (pattern.indexOf('!') === 0) {
395
468
  return dontWant.push(pattern.substring(1));
@@ -398,47 +471,71 @@ class Config {
398
471
  positiveWants.push(patternEntry);
399
472
  });
400
473
 
401
- dontWant = dontWant.map(p => p ? this.resolvePath(p) : p);
402
- Bluebird.reduce(positiveWants, (allThatIWant, patternEntry) => {
403
- let pattern = isa(patternEntry, String) ? patternEntry : patternEntry.src;
404
- let attrs = patternEntry.attrs || [];
405
- let patternUrl = url.parse(pattern);
406
-
407
- if (patternUrl.protocol === 'file:') {
408
- pattern = patternUrl.hostname + patternUrl.path;
409
- } else if (patternUrl.protocol) {
410
- return allThatIWant.concat({ src: pattern, attrs: attrs });
411
- }
474
+ dontWant = dontWant.map((p) => (p ? this.resolvePath(p) : p));
475
+ return reduce(
476
+ positiveWants,
477
+ (allThatIWant, patternEntry) => {
478
+ let pattern = isa(patternEntry, String)
479
+ ? patternEntry
480
+ : patternEntry.src;
481
+ let attrs = patternEntry.attrs || [];
482
+ let patternUrl = url.parse(pattern);
483
+
484
+ if (patternUrl.protocol === 'file:') {
485
+ pattern = patternUrl.hostname + patternUrl.path;
486
+ } else if (patternUrl.protocol) {
487
+ return allThatIWant.concat({ src: pattern, attrs: attrs });
488
+ }
412
489
 
413
- return globAsync(convertToPosix(this.resolvePath(pattern)), { ignore: dontWant.map(convertToPosix) })
414
- .then(files => files.sort())
415
- .then(files => allThatIWant.concat(files.map(f => {
416
- f = this.reverseResolvePath(f);
417
- return { src: f, attrs: attrs };
418
- })));
419
- }, [])
420
- .then(result => _.uniqBy(result, 'src'))
421
- .asCallback(callback);
490
+ return expandGlobPattern(
491
+ convertToPosix(this.resolvePath(pattern)),
492
+ dontWant.map(convertToPosix),
493
+ ).then((files) =>
494
+ allThatIWant.concat(
495
+ files.map((f) => {
496
+ f = this.reverseResolvePath(f);
497
+ return { src: f, attrs: attrs };
498
+ }),
499
+ ),
500
+ );
501
+ },
502
+ [],
503
+ )
504
+ .then((result) => _.uniqBy(result, 'src'))
505
+ .then(
506
+ (result) => {
507
+ if (callback) {
508
+ callback(null, result);
509
+ }
510
+ return result;
511
+ },
512
+ (err) => {
513
+ if (callback) {
514
+ callback(err);
515
+ }
516
+ throw err;
517
+ },
518
+ );
422
519
  }
423
520
 
424
521
  getSrcFiles(callback) {
425
522
  let srcFiles = this.get('src_files') || '*.js';
426
523
  let srcFilesIgnore = this.get('src_files_ignore') || '';
427
- this.getFileSet(srcFiles, srcFilesIgnore, callback);
524
+ return this.getFileSet(srcFiles, srcFilesIgnore, callback);
428
525
  }
429
526
 
430
527
  getFooterScripts(callback) {
431
528
  var want = this.get('footer_scripts') || [];
432
529
  var dontWant = this.get('src_files_ignore') || '';
433
530
 
434
- this.getFileSet(want, dontWant, callback);
531
+ return this.getFileSet(want, dontWant, callback);
435
532
  }
436
533
 
437
-
438
534
  getServeFiles(callback) {
439
535
  let want = this.get('serve_files') || this.get('src_files') || '*.js';
440
- let dontWant = this.get('serve_files_ignore') || this.get('src_files_ignore') || '';
441
- this.getFileSet(want, dontWant, callback);
536
+ let dontWant =
537
+ this.get('serve_files_ignore') || this.get('src_files_ignore') || '';
538
+ return this.getFileSet(want, dontWant, callback);
442
539
  }
443
540
 
444
541
  getUserDataDir() {
@@ -455,7 +552,7 @@ class Config {
455
552
 
456
553
  getCSSFiles(callback) {
457
554
  let want = this.get('css_files') || '';
458
- this.getFileSet(want, '', callback);
555
+ return this.getFileSet(want, '', callback);
459
556
  }
460
557
 
461
558
  getAllOptions() {
@@ -465,17 +562,23 @@ class Config {
465
562
  return;
466
563
  }
467
564
  if (o.options) {
468
- o.options.forEach(o => {
565
+ o.options.forEach((o) => {
469
566
  options.push(o.name());
470
567
  });
471
568
  }
472
569
  getOptions(o.parent);
473
570
  }
474
- getOptions(this.progOptions);
571
+ const progOpts = this.progOptions;
572
+ // In subcommand modes, progOptions is a plain object from optsWithGlobals() with
573
+ // a _commander reference pointing to the Command instance for option enumeration.
574
+ // In dev mode (no subcommand), progOptions is the Commander program object directly.
575
+ getOptions(
576
+ progOpts && progOpts._commander ? progOpts._commander : progOpts,
577
+ );
475
578
  return options;
476
579
  }
477
580
 
478
- getTemplateData(cb) {
581
+ async getTemplateData(cb) {
479
582
  let ret = {};
480
583
  let options = this.getAllOptions();
481
584
  let key;
@@ -490,24 +593,31 @@ class Config {
490
593
  for (key in this.config) {
491
594
  ret[key] = this.config[key];
492
595
  }
493
- this.getServeFiles((err, files) => {
494
- let replaceSlashes = f => ({
495
- src: f.src.replace(/\\/g, '/'),
496
- attrs: f.attrs
497
- });
498
596
 
499
- ret.serve_files = files.map(replaceSlashes);
500
-
501
- this.getCSSFiles((err, files) => {
502
- ret.css_files = files.map(replaceSlashes);
503
- this.getFooterScripts((err, files) => {
504
- ret.footer_scripts = files;
505
- if (cb) {
506
- cb(err, ret);
507
- }
508
- });
509
- });
597
+ const replaceSlashes = (f) => ({
598
+ src: f.src.replace(/\\/g, '/'),
599
+ attrs: f.attrs,
510
600
  });
601
+
602
+ try {
603
+ const [serveFiles, cssFiles, footerScripts] = await Promise.all([
604
+ this.getServeFiles(),
605
+ this.getCSSFiles(),
606
+ this.getFooterScripts(),
607
+ ]);
608
+
609
+ ret.serve_files = serveFiles.map(replaceSlashes);
610
+ ret.css_files = cssFiles.map(replaceSlashes);
611
+ ret.footer_scripts = footerScripts;
612
+
613
+ if (cb) {
614
+ cb(null, ret);
615
+ }
616
+ } catch (err) {
617
+ if (cb) {
618
+ cb(err, ret);
619
+ }
620
+ }
511
621
  }
512
622
  }
513
623
 
@@ -530,10 +640,13 @@ Config.prototype.defaults = {
530
640
  client_decycle_depth: 5,
531
641
  socket_heartbeat_timeout() {
532
642
  let browserDisconnectTimeout = this.get('browser_disconnect_timeout');
533
- let defaultBrowserDisconnectTimeout = this.defaults.browser_disconnect_timeout;
643
+ let defaultBrowserDisconnectTimeout =
644
+ this.defaults.browser_disconnect_timeout;
534
645
 
535
- return (browserDisconnectTimeout !== defaultBrowserDisconnectTimeout) ? browserDisconnectTimeout : 5;
536
- }
646
+ return browserDisconnectTimeout !== defaultBrowserDisconnectTimeout
647
+ ? browserDisconnectTimeout
648
+ : 5;
649
+ },
537
650
  };
538
651
 
539
652
  module.exports = Config;