backend-manager 3.2.131 → 3.2.133

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "3.2.131",
3
+ "version": "3.2.133",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
package/src/cli/cli.js CHANGED
@@ -49,8 +49,8 @@ let bem_allRulesBackupRegex = /({{\s*?backend-manager\s*?}})/sgm;
49
49
  let MOCHA_PKG_SCRIPT = 'mocha ../test/ --recursive --timeout=10000';
50
50
  let NPM_CLEAN_SCRIPT = 'rm -fr node_modules && rm -fr package-lock.json && npm cache clean --force && npm install && npm rb';
51
51
  let NOFIX_TEXT = chalk.red(`There is no automatic fix for this check.`);
52
- let runtimeconfigTemplate = JSON.parse((jetpack.read(path.resolve(`${__dirname}/../../templates/runtimeconfig.json`))) || '{}');
53
- let bemConfigTemplate = JSON.parse((jetpack.read(path.resolve(`${__dirname}/../../templates/backend-manager-config.json`))) || '{}');
52
+ let runtimeconfigTemplate = loadJSON(`${__dirname}/../../templates/runtimeconfig.json`);
53
+ let bemConfigTemplate = loadJSON(`${__dirname}/../../templates/backend-manager-config.json`);
54
54
 
55
55
  function Main() {
56
56
  }
@@ -236,35 +236,32 @@ Main.prototype.getRulesFile = function () {
236
236
  Main.prototype.setup = async function () {
237
237
  const self = this;
238
238
  let cwd = jetpack.cwd();
239
+
239
240
  log(chalk.green(`\n---- RUNNING SETUP v${self.default.version} ----`));
240
- self.package = jetpack.read(`${self.firebaseProjectPath}/functions/package.json`) || '{}';
241
- self.firebaseJSON = jetpack.read(`${self.firebaseProjectPath}/firebase.json`) || '{}';
242
- self.firebaseRC = jetpack.read(`${self.firebaseProjectPath}/.firebaserc`) || '{}';
243
- self.runtimeConfigJSON = jetpack.read(`${self.firebaseProjectPath}/functions/.runtimeconfig.json`) || '{}';
244
- self.remoteconfigJSON = jetpack.read(`${self.firebaseProjectPath}/remoteconfig.template.json`) || '{}';
245
- self.projectPackage = jetpack.read(`${self.firebaseProjectPath}/package.json`) || '{}';
246
- self.bemConfigJSON = jetpack.read(`${self.firebaseProjectPath}/functions/backend-manager-config.json`) || '{}';
247
241
 
242
+ // Load files
243
+ self.package = loadJSON(`${self.firebaseProjectPath}/functions/package.json`);
244
+ self.firebaseJSON = loadJSON(`${self.firebaseProjectPath}/firebase.json`);
245
+ self.firebaseRC = loadJSON(`${self.firebaseProjectPath}/.firebaserc`);
246
+ self.runtimeConfigJSON = loadJSON(`${self.firebaseProjectPath}/functions/.runtimeconfig.json`);
247
+ self.remoteconfigJSON = loadJSON(`${self.firebaseProjectPath}/remoteconfig.template.json`);
248
+ self.projectPackage = loadJSON(`${self.firebaseProjectPath}/package.json`);
249
+ self.bemConfigJSON = loadJSON(`${self.firebaseProjectPath}/functions/backend-manager-config.json`);
248
250
  self.gitignore = jetpack.read(`${self.firebaseProjectPath}/functions/.gitignore`) || '';
249
- if (!self.package) {
251
+
252
+ // Check if package exists
253
+ if (!hasContent(self.package)) {
250
254
  log(chalk.red(`Missing functions/package.json :(`));
251
255
  return;
252
256
  }
253
- // console.log('cwd', cwd, cwd.endsWith('functions'));
257
+
258
+ // Check if we're running from the functions folder
254
259
  if (!cwd.endsWith('functions') && !cwd.endsWith('functions/')) {
255
260
  log(chalk.red(`Please run ${chalk.bold('npx bm setup')} from the ${chalk.bold('functions')} folder. Run ${chalk.bold('cd functions')}.`));
256
261
  return;
257
262
  }
258
263
 
259
- self.package = JSON.parse(self.package);
260
- self.firebaseJSON = JSON.parse(self.firebaseJSON);
261
- self.firebaseRC = JSON.parse(self.firebaseRC);
262
- self.runtimeConfigJSON = JSON.parse(self.runtimeConfigJSON);
263
- self.remoteconfigJSON = JSON.parse(self.remoteconfigJSON);
264
- self.projectPackage = JSON.parse(self.projectPackage);
265
-
266
- self.remoteconfigJSONExists = Object.keys(self.remoteconfigJSON).length > 0;
267
-
264
+ // Load the rules files
268
265
  self.getRulesFile();
269
266
 
270
267
  self.default.rulesVersionRegex = new RegExp(`///---version=${self.default.version}---///`)
@@ -275,8 +272,7 @@ Main.prototype.setup = async function () {
275
272
  self.projectName = self.firebaseRC.projects.default;
276
273
  self.projectUrl = `https://console.firebase.google.com/project/${self.projectName}`;
277
274
 
278
- self.bemApiURL = `https://us-central1-${_.get(self.firebaseRC, 'projects.default')}.cloudfunctions.net/bm_api?authenticationToken=${_.get(self.runtimeConfigJSON, 'backend_manager.key')}`;
279
- // const prepareStatsURL = `https://us-central1-${_.get(self.firebaseRC, 'projects.default')}.cloudfunctions.net/bm_api?authenticationToken=undefined`;
275
+ self.bemApiURL = `https://us-central1-${self?.firebaseRC?.projects?.default}.cloudfunctions.net/bm_api?authenticationToken=${self?.runtimeConfigJSON?.backend_manager?.key}`;
280
276
 
281
277
  // Log
282
278
  log(`ID: `, chalk.bold(`${self.projectName}`));
@@ -403,63 +399,51 @@ Main.prototype.setup = async function () {
403
399
  }, fix_distScript);
404
400
 
405
401
  await self.test('using proper .runtimeconfig', async function () {
406
- let runtimeconfig = JSON.parse(jetpack.read(`${self.firebaseProjectPath}/functions/.runtimeconfig.json`) || '{}');
407
- let ogPaths = getObjectPaths(runtimeconfigTemplate).split('\n');
402
+ // Set pass
408
403
  let pass = true;
409
404
 
410
- for (var i = 0, l = ogPaths.length; i < l; i++) {
411
- let item = ogPaths[i];
412
- if (!item) {continue}
413
- pass = _.get(runtimeconfig, item, undefined);
414
- if (typeof pass === 'undefined') {
415
- break;
405
+ // Loop through all the keys in the template
406
+ powertools.getKeys(runtimeconfigTemplate).forEach((key) => {
407
+ const userValue = _.get(self.runtimeConfigJSON, key, undefined);
408
+
409
+ // If the user value is undefined, then we need to set pass to false
410
+ if (typeof userValue === 'undefined') {
411
+ pass = false;
416
412
  }
417
- }
413
+ });
418
414
 
419
- return !!pass;
415
+ // Return result
416
+ return pass;
420
417
  }, fix_runtimeConfig);
421
418
 
422
- // await self.test('using proper env', async function () {
423
- // let runtimeconfig = JSON.parse(jetpack.read(`${self.firebaseProjectPath}/functions/.runtimeconfig.json`) || '{}');
424
- // let ogPaths = getObjectPaths(runtimeconfigTemplate).split('\n');
425
- // let pass = true;
426
-
427
- // for (var i = 0, l = ogPaths.length; i < l; i++) {
428
- // let item = ogPaths[i];
429
- // if (!item) {continue}
430
- // pass = _.get(runtimeconfig, item, undefined);
431
- // if (typeof pass === 'undefined') {
432
- // break;
433
- // }
434
- // }
435
-
436
- // return !!pass;
437
- // }, fix_runtimeConfig);
438
-
439
419
  await self.test('using proper backend-manager-config.json', async function () {
440
- const bemConfig = JSON.parse(self.bemConfigJSON);
441
-
442
- let ogPaths = getObjectPaths(bemConfigTemplate).split('\n');
420
+ // Set pass
443
421
  let pass = true;
444
422
 
445
- for (var i = 0, l = ogPaths.length; i < l; i++) {
446
- let item = ogPaths[i];
447
- if (!item) {continue}
448
- pass = (_.get(bemConfig, item, undefined));
449
- if (typeof pass === 'undefined' || typeof pass === '') {
450
- break;
423
+ // Loop through all the keys in the template
424
+ powertools.getKeys(bemConfigTemplate).forEach((key) => {
425
+ const userValue = _.get(self.bemConfigJSON, key, undefined);
426
+
427
+ // If the user value is undefined, then we need to set pass to false
428
+ if (typeof userValue === 'undefined') {
429
+ pass = false;
451
430
  }
452
- }
431
+ });
453
432
 
454
- if (self.projectName !== bemConfig.firebaseConfig.projectId) {
433
+ // Return result
434
+ return pass;
435
+ }, fix_bemConfig);
436
+
437
+ await self.test('has correct ID in backend-manager-config.json', async function () {
438
+ // Check if the project name matches the projectId
439
+ if (self.projectName !== self.bemConfigJSON?.firebaseConfig?.projectId) {
455
440
  console.error(chalk.red('Mismatch between project name and firebaseConfig.projectId in backend-manager-config.json'));
456
441
  return false;
457
442
  }
458
443
 
459
- self.bemConfigJSON = bemConfig;
460
-
461
- return !!pass;
462
- }, fix_bemConfig);
444
+ // Return pass
445
+ return true;
446
+ }, NOFIX);
463
447
 
464
448
  await self.test('has service-account.json', function () {
465
449
  let exists = jetpack.exists(`${self.firebaseProjectPath}/functions/service-account.json`);
@@ -478,35 +462,28 @@ Main.prototype.setup = async function () {
478
462
  }
479
463
  }, fix_gitignore);
480
464
 
481
-
482
465
  // Check firebase.json fields
483
- await self.test('firestore rules in JSON', function () {
484
- const firestore = _.get(self.firebaseJSON, 'firestore', {});
485
- return (firestore.rules === 'firestore.rules')
466
+ await self.test('firestore rules in JSON', () => {
467
+ return self.firebaseJSON?.firestore?.rules === 'firestore.rules'
486
468
  }, fix_firestoreRules);
487
469
 
488
- await self.test('firestore indexes in JSON', function () {
489
- let firestore = _.get(self.firebaseJSON, 'firestore', {});
490
- return (firestore.indexes === 'firestore.indexes.json')
470
+ await self.test('firestore indexes in JSON', () => {
471
+ return self.firebaseJSON?.firestore?.indexes === 'firestore.indexes.json';
491
472
  }, fix_firestoreIndexes);
492
473
 
493
- await self.test('realtime rules in JSON', function () {
494
- const database = _.get(self.firebaseJSON, 'database', {});
495
- return (database.rules === 'database.rules.json')
474
+ await self.test('realtime rules in JSON', () => {
475
+ return self.firebaseJSON?.database?.rules === 'database.rules.json';
496
476
  }, fix_realtimeRules);
497
477
 
498
- await self.test('storage rules in JSON', function () {
499
- const storage = _.get(self.firebaseJSON, 'storage', {});
500
- return (storage.rules === 'storage.rules')
478
+ await self.test('storage rules in JSON', () => {
479
+ return self.firebaseJSON?.storage?.rules === 'storage.rules';
501
480
  }, fix_storageRules);
502
481
 
503
- await self.test('remoteconfig template in JSON', function () {
504
- const remoteconfig = _.get(self.firebaseJSON, 'remoteconfig', {});
505
-
506
- if (self.remoteconfigJSONExists) {
507
- return (remoteconfig.template === 'remoteconfig.template.json')
482
+ await self.test('remoteconfig template in JSON', () => {
483
+ if (hasContent(self.remoteconfigJSON)) {
484
+ return self.firebaseJSON?.remoteconfig?.template === 'remoteconfig.template.json';
508
485
  } else {
509
- return (remoteconfig.template === '')
486
+ return self.firebaseJSON?.remoteconfig?.template === '';
510
487
  }
511
488
  }, fix_remoteconfigTemplate);
512
489
 
@@ -676,29 +653,6 @@ Main.prototype.setup = async function () {
676
653
 
677
654
  };
678
655
 
679
- // https://stackoverflow.com/questions/41802259/javascript-deep-check-objects-have-same-keys
680
- // function objectsHaveSameKeys(...objects) {
681
- // let objectPaths = getObjectPaths()
682
- // // const allKeys = objects.reduce((keys, object) => keys.concat(Object.keys(object)), []);
683
- // // const union = new Set(allKeys);
684
- // // return objects.every(object => union.size === Object.keys(object).length);
685
- //
686
- // // const allKeys = objects.reduce((keys, object) => keys.concat(Object.keys(object)), []);
687
- // console.log('allKeys', allKeys);
688
- // return false
689
- // }
690
-
691
- function getObjectPaths(object, parent) {
692
- let keys = Object.keys(object);
693
- let composite = '';
694
- parent = parent || '';
695
- for (var i = 0, l = keys.length; i < l; i++) {
696
- let item = object[keys[i]];
697
- composite += typeof item === 'object' ? getObjectPaths(item, keys[i]) : `${parent}.${keys[i]}\n`;
698
- }
699
- return composite;
700
- }
701
-
702
656
  Main.prototype.test = async function(name, fn, fix, args) {
703
657
  const self = this;
704
658
  let status;
@@ -749,47 +703,50 @@ function bemPackageVersionWarning(package, current, latest) {
749
703
 
750
704
  async function fix_runtimeConfig(self) {
751
705
  return new Promise(function(resolve, reject) {
706
+ // Log
752
707
  log(NOFIX_TEXT);
753
708
  log(chalk.red(`You need to run ${chalk.bold(`npx bm config:set`)} for each of these keys:`));
754
- let objectKeys = getObjectPaths(runtimeconfigTemplate).split('\n');
755
- let theirConfig = JSON.parse(jetpack.read(`${self.firebaseProjectPath}/functions/.runtimeconfig.json`) || '{}');
756
- for (var i = 0, l = objectKeys.length; i < l; i++) {
757
- let item = objectKeys[i];
758
- if (!item) {return}
759
- let has = _.get(theirConfig, item, '');
760
- if (has) {
761
- log(chalk.red(`${item} (${has})`));
709
+
710
+ // Log what keys are missing
711
+ powertools.getKeys(runtimeconfigTemplate).forEach((key) => {
712
+ const userValue = _.get(self.runtimeConfigJSON, key, undefined);
713
+
714
+ if (typeof userValue === 'undefined') {
715
+ log(chalk.red.bold(`${key}`));
762
716
  } else {
763
- log(chalk.red.bold(`${item}`));
717
+ log(chalk.red(`${key} (${userValue})`));
764
718
  }
765
- }
766
- // console.log('objectKeys', objectKeys);
767
- // log(chalk.red(`You need to run ${chalk.bold(`npx bm config:set`)} for each of these keys: \n${getObjectPaths(runtimeconfigTemplate)}`));
719
+ });
720
+
721
+ // Reject
768
722
  reject();
769
723
  });
770
724
  };
771
725
 
772
726
  async function fix_bemConfig(self) {
773
727
  return new Promise(function(resolve, reject) {
728
+ // Log
774
729
  log(NOFIX_TEXT);
775
730
  log(chalk.red(`You need to open backend-manager-config.json and set each of these keys:`));
776
- let objectKeys = getObjectPaths(bemConfigTemplate).split('\n');
777
- let theirConfig = JSON.parse(jetpack.read(`${self.firebaseProjectPath}/functions/backend-manager-config.json`) || '{}');
778
- if (Object.keys(theirConfig).length < 1) {
779
- jetpack.write(`${self.firebaseProjectPath}/functions/backend-manager-config.json`, bemConfigTemplate)
731
+
732
+ // Write if it doesnt exist
733
+ if (!hasContent(self.bemConfigJSON)) {
734
+ // jetpack.write(`${self.firebaseProjectPath}/functions/backend-manager-config.json`, bemConfigTemplate)
735
+ jetpack.write(`${self.firebaseProjectPath}/functions/backend-manager-config.json`, {})
780
736
  }
781
- for (var i = 0, l = objectKeys.length; i < l; i++) {
782
- let item = objectKeys[i];
783
- if (!item) {return}
784
- let has = _.get(theirConfig, item, '');
785
- if (has) {
786
- log(chalk.red(`${item} (${has})`));
737
+
738
+ // Log what keys are missing
739
+ powertools.getKeys(bemConfigTemplate).forEach((key) => {
740
+ const userValue = _.get(self.bemConfigJSON, key, undefined);
741
+
742
+ if (typeof userValue === 'undefined') {
743
+ log(chalk.red.bold(`${key}`));
787
744
  } else {
788
- log(chalk.red.bold(`${item}`));
745
+ log(chalk.red(`${key} (${userValue})`));
789
746
  }
790
- }
791
- // console.log('objectKeys', objectKeys);
792
- // log(chalk.red(`You need to run ${chalk.bold(`bm config:set`)} for each of these keys: \n${getObjectPaths(runtimeconfigTemplate)}`));
747
+ });
748
+
749
+ // Reject
793
750
  reject();
794
751
  });
795
752
  };
@@ -974,7 +931,8 @@ function fix_storageRules(self) {
974
931
 
975
932
  function fix_remoteconfigTemplate(self) {
976
933
  return new Promise(function(resolve, reject) {
977
- _.set(self.firebaseJSON, 'remoteconfig.template', self.remoteconfigJSONExists ? 'remoteconfig.template.json' : '')
934
+
935
+ _.set(self.firebaseJSON, 'remoteconfig.template', hasContent(self.remoteconfigJSON) ? 'remoteconfig.template.json' : '')
978
936
  jetpack.write(`${self.firebaseProjectPath}/firebase.json`, JSON.stringify(self.firebaseJSON, null, 2));
979
937
  resolve();
980
938
  });
@@ -1538,26 +1496,26 @@ function uninstallPkg(name) {
1538
1496
  });
1539
1497
  });
1540
1498
  }
1499
+ function loadJSON(path) {
1500
+ const contents = jetpack.read(path);
1501
+ if (!contents) {
1502
+ return {};
1503
+ }
1504
+
1505
+ return JSON5.parse(contents);
1506
+ }
1507
+
1508
+ function hasContent(object) {
1509
+ return Object.keys(object).length > 0;
1510
+ }
1541
1511
 
1542
1512
  function cleanOutput(data) {
1543
1513
  try {
1544
- // data = (data + '').replace('\n', '')
1545
1514
  data = (data + '').replace(/\n$/, '')
1546
1515
  } catch (e) {
1547
1516
 
1548
1517
  }
1518
+
1519
+ // Return
1549
1520
  return data;
1550
- // try {
1551
- // data = data.replace(/\n/, '');
1552
- // } catch (e) {
1553
- //
1554
- // } finally {
1555
- //
1556
- // }
1557
- // return data;
1558
- // // if (typeof data !== 'string') {
1559
- // // return data;
1560
- // // } else {
1561
- // // return data.replace(/\n/, '');
1562
- // // }
1563
1521
  }
@@ -299,8 +299,11 @@ Module.prototype.uploadPost = function (content) {
299
299
  // Log
300
300
  assistant.log(`uploadPost(): Existing`, existing);
301
301
 
302
- // Error
303
- if (existing instanceof Error) {
302
+ // Quit if error and it's not a 404
303
+ if (
304
+ existing instanceof Error
305
+ && existing?.status !== 404
306
+ ) {
304
307
  return reject(existing);
305
308
  }
306
309
 
@@ -309,7 +312,7 @@ Module.prototype.uploadPost = function (content) {
309
312
  owner: owner,
310
313
  repo: repo,
311
314
  path: filename,
312
- sha: existing?.data?.sha,
315
+ sha: existing?.data?.sha || undefined,
313
316
  message: `📦 admin:create-post:upload-post ${filename}`,
314
317
  content: Buffer.from(content).toString('base64'),
315
318
  })
@@ -429,6 +429,9 @@ BackendAssistant.prototype.errorify = function (e, options) {
429
429
  sendable = `${sendable} (${newError.tag})`;
430
430
  }
431
431
 
432
+ // Clear log prefix before sending
433
+ self.clearLogPrefix();
434
+
432
435
  // Log
433
436
  if (options.log) {
434
437
  self.log(`Sending response (${options.code}):`, JSON.stringify(sendable));
@@ -492,12 +495,16 @@ BackendAssistant.prototype.respond = function(response, options) {
492
495
  // Send response
493
496
  res.status(options.code);
494
497
 
498
+ // Log function
495
499
  function _log(text) {
496
500
  if (options.log) {
497
501
  self.log(`${text} (${options.code}):`, JSON.stringify(response));
498
502
  }
499
503
  }
500
504
 
505
+ // Clear log prefix before sending
506
+ self.clearLogPrefix();
507
+
501
508
  // Redirect
502
509
  const isRedirect = isBetween(options.code, 300, 399);
503
510
  if (isRedirect) {
@@ -1,36 +1,42 @@
1
1
  {
2
- "app": {
3
- "id": "my-app"
4
- },
5
- "brand": {
6
- "name": "My Brand",
7
- "url": "https://example.com",
8
- "email": "support@example.com",
9
- "wordmark": "https://example.com/wordmark.png",
10
- "brandmark": "https://example.com/wordmark.png",
11
- "combomark": "https://example.com/combomark.png"
12
- },
13
- "mailchimp": {
14
- "list_id": "36774bf825"
15
- },
16
- "github": {
17
- "user": "username",
18
- "repo_website": "https://github.com/username/backend-manager"
19
- },
20
- "sentry": {
21
- "dsn": "https://d965557418748jd749d837asf00552f@o777489.ingest.sentry.io/8789941"
22
- },
23
- "google_analytics": {
24
- "id": "UA-123456789-1",
25
- "secret": "ABCx1234567890ABCDEFGH"
26
- },
27
- "firebaseConfig": {
28
- "apiKey": "123-456",
29
- "authDomain": "PROJECT-ID.firebaseapp.com",
30
- "projectId": "PROJECT-ID",
31
- "storageBucket": "PROJECT-ID.appspot.com",
32
- "messagingSenderId": "123",
33
- "appId": "1:123:web:456",
34
- "measurementId": "G-0123456789"
35
- }
2
+ app: {
3
+ id: 'my-app',
4
+ },
5
+ brand: {
6
+ name: 'My Brand',
7
+ url: 'https://example.com',
8
+ email: 'support@example.com',
9
+ wordmark: 'https://example.com/wordmark.png',
10
+ brandmark: 'https://example.com/wordmark.png',
11
+ combomark: 'https://example.com/combomark.png',
12
+ },
13
+ mailchimp: {
14
+ list_id: '36774bf825',
15
+ },
16
+ github: {
17
+ user: 'username',
18
+ repo_website: 'https://github.com/username/backend-manager',
19
+ },
20
+ sentry: {
21
+ dsn: 'https://d965557418748jd749d837asf00552f@o777489.ingest.sentry.io/8789941',
22
+ },
23
+ google_analytics: {
24
+ id: 'UA-123456789-1',
25
+ secret: 'ABCx1234567890ABCDEFGH',
26
+ },
27
+ firebaseConfig: {
28
+ apiKey: '123-456',
29
+ authDomain: 'PROJECT-ID.firebaseapp.com',
30
+ projectId: 'PROJECT-ID',
31
+ storageBucket: 'PROJECT-ID.appspot.com',
32
+ messagingSenderId: '123',
33
+ appId: '1:123:web:456',
34
+ measurementId: 'G-0123456789',
35
+ },
36
+ ghostii: {
37
+ articles: 1,
38
+ sources: [
39
+ '$app',
40
+ ]
41
+ },
36
42
  }
@@ -1,12 +1,12 @@
1
1
  {
2
- "mailchimp": {
3
- "key": "da69e4758adb4839201bcda8565782d4-us16"
2
+ mailchimp: {
3
+ key: 'da69e4758adb4839201bcda8565782d4-us16',
4
4
  },
5
- "backend_manager": {
6
- "key": "api_254448f1-4738-8884-88f7-8884jdd9cc96",
7
- "namespace": "e69776e2-9d14-4486-88d0-aa84426d0882"
5
+ backend_manager: {
6
+ key: 'api_254448f1-4738-8884-88f7-8884jdd9cc96',
7
+ namespace: 'e69776e2-9d14-4486-88d0-aa84426d0882',
8
+ },
9
+ github: {
10
+ key: '7f7f80bafc6689895jf86937fg444f6fd5',
8
11
  },
9
- "github": {
10
- "key": "7f7f80bafc6689895jf86937fg444f6fd5"
11
- }
12
12
  }