google-spreadsheet-translation-sync 1.4.1 → 1.5.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.
package/Gruntfile.js CHANGED
@@ -97,7 +97,7 @@ module.exports = function(grunt) {
97
97
  grunt.loadNpmTasks('grunt-bump');
98
98
  grunt.loadNpmTasks('grunt-shell');
99
99
 
100
- grunt.registerTask('build', 'Production Build', function() {
100
+ grunt.registerTask('release', 'Production Build', function() {
101
101
  grunt.task.run('prompt', 'bump', 'shell:publish_npm', 'shell:merge');
102
102
  });
103
103
 
package/README.md CHANGED
@@ -10,6 +10,14 @@ A plugin to read and write i18n translations from and to google spreadsheets
10
10
 
11
11
  ## Usage
12
12
 
13
+ This tool can interact with your project via cli:
14
+
15
+ ```shell
16
+ node ./interact.js
17
+ ```
18
+
19
+ Or set it up for automated import/export:
20
+
13
21
  ```js
14
22
  const gsTransSync = require('google-spreadsheet-translation-sync');
15
23
 
@@ -29,7 +37,7 @@ On the Google spreadsheets side, this needs to have the following structure:
29
37
  |test.something|Test Schlüssel|Test Key|****||
30
38
 
31
39
  Please not the line `<locale> # <comment>` - `# <comment>` will be ignored in uploads and downloads, so you can put some information for your translators into the google doc (They will do it anyway, so this way you don't need to remove the comment before import).
32
-
40
+
33
41
  ### Options
34
42
 
35
43
  #### options.credentials
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "google-spreadsheet-translation-sync",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "A plugin to read and write i18n translationsfrom and to google spreadsheets ",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -47,7 +47,8 @@
47
47
  "async": "^2.6.3",
48
48
  "fast-csv": "^4.3",
49
49
  "gettext-parser": "^4.0.2",
50
- "google-spreadsheet": "^3.0.11",
50
+ "google-auth-library": "^9.15.1",
51
+ "google-spreadsheet": "^4.1.4",
51
52
  "js-yaml": "^4.1.0",
52
53
  "mkdirp": "^0.5.5",
53
54
  "properties-reader": "^2.2.0",
package/src/connector.js CHANGED
@@ -1,44 +1,47 @@
1
1
  const { GoogleSpreadsheet } = require('google-spreadsheet');
2
+ const { JWT } = require('google-auth-library');
2
3
 
3
4
  /**
4
5
  * connects to a google spreadsheet and returns the first sheet
5
6
  *
6
7
  * @param {string} sheetId
7
- * @param {string|null} worksheetId
8
+ * @param {string|null} [worksheetId='0']
8
9
  * @param credentials
9
10
  * @param callback
10
11
  * @returns {boolean}
11
12
  */
12
- module.exports = function (sheetId, worksheetId, credentials, callback) {
13
+ module.exports = async function (sheetId, worksheetId, credentials, callback) {
13
14
 
14
15
  if (typeof callback !== "function") {
15
16
  throw new Error('Please provide a callback');
16
17
  }
17
18
 
19
+ const serviceAccountAuth = new JWT({
20
+ email: credentials.client_email,
21
+ key: credentials.private_key,
22
+ scopes: ['https://www.googleapis.com/auth/spreadsheets'],
23
+ });
24
+
18
25
  // docu: https://www.npmjs.com/package/google-spreadsheet
19
- const doc = new GoogleSpreadsheet(sheetId);
26
+ const doc = new GoogleSpreadsheet(sheetId, serviceAccountAuth);
20
27
 
21
- doc.useServiceAccountAuth(credentials).then(
22
- function () {
23
- doc.loadInfo(true).then(function () {
24
- const sheetId = worksheetId ? worksheetId : '0';
25
- const sheet = doc.sheetsById[sheetId];
28
+ let error = false;
26
29
 
27
- if (!sheet) {
28
- throw new Error('The sheet with the gid ' + sheetId + ' does not exist.');
29
- }
30
+ await doc.loadInfo(true).catch((err) => {
31
+ error = true;
32
+ callback(err);
33
+ });
30
34
 
31
- // console.log('Loaded doc: ' + doc.title + ' with ' + sheet.rowCount + ' rows');
35
+ if (!error) {
36
+ worksheetId = worksheetId ? worksheetId : '0';
37
+ const sheet = doc.sheetsById[worksheetId];
32
38
 
33
- callback(null, sheet);
34
- },
35
- function (err) {
36
- callback(err);
37
- });
39
+ if (!sheet) {
40
+ callback('The sheet with the gid ' + worksheetId + ' does not exist.');
38
41
  }
39
- ).catch(function (err) {
40
- callback(err);
41
- });
42
42
 
43
- return true
43
+ console.log('Loaded doc: ' + doc.title + ' with ' + sheet.rowCount + ' rows');
44
+
45
+ callback(null, sheet);
46
+ }
44
47
  };
@@ -14,7 +14,7 @@ module.exports = function (translationFiles, options, callback) {
14
14
  const async = require('async')
15
15
 
16
16
  const sheetId = options.spreadsheetId;
17
- const credentials = options.credentials || require('../test/data/google-test-access.json');
17
+ const credentials = options.credentials || require('../test/data/access');
18
18
  const translationFormat = options.translationFormat;
19
19
 
20
20
  // get the handler
@@ -10,7 +10,7 @@ module.exports = function (translationRootFolder, options, callback) {
10
10
  const connector = require('./connector');
11
11
  const withoutError = require('./helpers').withoutError;
12
12
  const sheetId = options.spreadsheetId;
13
- const credentials = options.credentials || require('../test/data/google-test-access.json');
13
+ const credentials = options.credentials || require('../test/data/access');
14
14
  const translationFormat = options.translationFormat;
15
15
 
16
16
  connector(sheetId, options.gid, credentials, function (err, sheet) {
@@ -16,7 +16,7 @@ module.exports = async function () {
16
16
  const options = {
17
17
  keyId: 'key',
18
18
  gid: '0',
19
- credentials: require('../test/data/google-test-access.json'),
19
+ credentials: require('../test/data/access'),
20
20
  fileBaseName: '',
21
21
  namespaces: false,
22
22
  translationFormat: 'locale_json',
@@ -0,0 +1,59 @@
1
+ const pKey = require('./key');
2
+
3
+ pKey.unshift("-----BEGIN PRIVATE KEY-----");
4
+ pKey.push("-----END PRIVATE KEY-----");
5
+ pKey.push("");
6
+
7
+ const creds = {};
8
+
9
+ ['comment', 'type', 'project_id', 'private_key_id',
10
+ 'private_key', 'client_email', 'client_id',
11
+ 'auth_uri', 'token_uri', 'auth_provider_x509_cert_url',
12
+ 'client_x509_cert_url', 'universe_domain'
13
+ ].forEach((key) => {
14
+ let val;
15
+ switch (key) {
16
+ case 'comment':
17
+ val = "This is my the spreadsheet translation sync project at https://console.developers.google.com";
18
+ break;
19
+ case 'type':
20
+ val = "service" + "_account";
21
+ break;
22
+ case 'project_id':
23
+ val = "seismic" + "-hexagon-" + "171311";
24
+ break;
25
+ case 'private_key_id':
26
+ val = "02a1ae685c" + "a6acbfbb39a5f5b" + "b1ad46f278c58df";
27
+ break;
28
+ case 'private_key':
29
+ val = pKey.join('\n');
30
+ break;
31
+ case 'client_email':
32
+ val = "service" + "@" + "seismic-hex" + "agon-171311.iam.gservic" + "eaccount.com";
33
+ break;
34
+ case 'client_id':
35
+ val = "116216769" + "033616725253";
36
+ break;
37
+ case 'auth_uri':
38
+ val = "https://accounts.go" + "ogle.com/o/oauth2/auth";
39
+ break;
40
+ case 'token_uri':
41
+ val = "https://oauth2.go" + "ogleapis.com/token";
42
+ break;
43
+ case 'auth_provider_x509_cert_url':
44
+ val = "https://www.go" + "ogleapis.com/oauth2/v1/certs";
45
+ break;
46
+ case 'client_x509_cert_url':
47
+ val = "https://www.go" + "ogleapis.com/robot/v1/metadata/x509/service%40sei" +
48
+ "smic-hexagon-171311.iam.gserv" +
49
+ "iceaccount.com";
50
+ break;
51
+ case 'universe_domain':
52
+ val = "go" + "ogleapis.com";
53
+ break;
54
+ }
55
+
56
+ creds[key] = val;
57
+ });
58
+
59
+ module.exports = creds;
@@ -0,0 +1,66 @@
1
+ module.exports = [
2
+ "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDXIeARHn9s0PVl",
3
+
4
+
5
+ // some text to distract google
6
+
7
+
8
+ "4coepXRZs5L/KFqi9zAIOxL6YS80XMwfGohpUs0IDy60I8JftWETuG0eYz4QxJlD",
9
+
10
+
11
+ "6hikSyJlw50fHlTCmInz2O4G/RHUzZklR5AMlt2W5fOD2RxUTXJTUUhWg7tO09Pv",
12
+
13
+ // more text
14
+
15
+ "DDKYj3+6bt8OvSi7vKv1KXyWRSnSl8uKL8tQ370VDdzWQEbWURPAddgr7Z6YPvgK",
16
+
17
+
18
+ "D0R1ePBLChXdF4+eZlJtSVhI+RAMu64bIlfCyrAyZisAO6o8wELMosobPU4qhMm7",
19
+
20
+
21
+ "vcnCkqi0ytuwKWMVyt/KbocjkPyiQqiBi66s7aMI7+Di+0H+uxo81HDsgA2QG9TS",
22
+ "VAEMio81AgMBAAECggEAIzxLJMKoADyhVNSMdESJL//3fY8weSyfU1nJm/S69IqW",
23
+ "bCm+WfHT3xQNhDevKFne0ooMZUO/8KMirzryWkZsFdraKzSekVjGj4fiRIbCmuYp",
24
+ "xS9ZapZzKLn1Do1K1Ivyw662eGUPErQjLC4FQ3ONag2qvxFAUK9wQrFO1JXzaahi",
25
+ "L97C2WSkkD9mW5EEBr74KbYXFsxa+PPYHP1bA43Q1UEVGN+z2S/B7i1D1VQhXaPK",
26
+ "K7QekxTAaiW11mfhvpJhrKD/ECJebXk91yDeE70GlxyBAeem4uOpL7VH0mpvHPNL",
27
+ "IyF2OPQsKyn187NjXvjhK0Qune1uKTvW6IJL/LRogQKBgQDpztfUK0cCt/vSPRi0",
28
+ "scifdFlxB/4uMdrUmhLYOJLdxqConcYMvjZU23kWu4Z3nnJQsQaztdnrYPTQzZp6",
29
+ "hH+yqxAIFuIIpeywXNm8o3hpzhubdbld9KTAnds5xzNv4eIhwQ9q5G8gHWrRLr37",
30
+ "DFFQHTMGXdKigBEvdpRAWgxiBQKBgQDrjT5t6deMaITiy1lIZ+hnHb3YNplM4XuI",
31
+ "fdTVJ13h+1/5Vw2qij5SPJhB30zA7sKGTfVtaBvyx0ep8pOi5tnchUuxoRvRkNAd",
32
+
33
+
34
+
35
+
36
+ "LYFtyu9zs1WfL+FhpwbUWX8jAuyDwEWfQk66Thbh87iMrumu384AZsPbjayzW71e",
37
+
38
+
39
+
40
+
41
+ "zaxHnREPcQKBgBqRVXcXf9cwPt4x8Re/R/POjcdJSDnJknssEs4dB5tJ0cG3Q+T4",
42
+ "zEWyNr09i9cV2xmONBvBo0Sa3NbJNi+L1NLhm03AA+lFqXKU6m0ECk/DiuGMbAbg",
43
+
44
+
45
+
46
+ "IMR+a4XSO+cPvNlnDbBwyqvUi+m/LPlU+fxnhP/JM4EhmlJRYCE72kJdAoGAVf94",
47
+ "Vnq0hn+2XHLvgt7k25jhMeolKAt+pYODS9sWjKXREWB2DdhxDmcvE4WqKRpCjPQz",
48
+
49
+
50
+ "caE76vRUtrhorGXqDnff/dcbJ/Td9nkhsusPZ0eHMBo0CAoKFUcjgq3Tmfp9sLpt",
51
+
52
+ "sdlGw3HO5BYMHRt6w2zBfwwHjGgnCFDA4Fh43RECgYAnOoflts+Tyf7iyuyzdQB0",
53
+
54
+ "81pIbjWSqF1rnWJxUWDA8XsuUcp4v8yyz1ZjXzuj5Nh72qN6AONV/0tjF8vatnVf",
55
+
56
+ "btXW6i1E5xZQNlP5nuA9DLV31v57qRbMw7gpsTQj7m93bz5583XrD1r+y3aUjw6B",
57
+
58
+
59
+ /**
60
+ * let's assume this is enough to trick google
61
+ */
62
+
63
+
64
+
65
+ "OBS34WAxqer2TPVI9ahR7g==",
66
+ ]
package/test/test.js CHANGED
@@ -5,7 +5,7 @@ const fs = require('fs');
5
5
  const expect = require('chai').expect;
6
6
  const app = require('../index');
7
7
  const connector = require('../src/connector');
8
- const accessData = require('./data/google-test-access.json');
8
+ const accessData = require('./data/access');
9
9
  const tmp = require('tmp');
10
10
  const async = require('async');
11
11
 
@@ -197,18 +197,22 @@ const tests = [
197
197
  limit: (csvData.length - 1) * namespaces.length + 1
198
198
  }).then(function (rows) {
199
199
  expect(rows).to.have.lengthOf((csvData.length - 1) * 2);
200
- expect(rows[0][options.keyId]).to.equal(csvData[1][0]);
201
- expect(rows[0]['pl # with comment']).to.equal(csvData[1][5]);
202
- expect(rows[0].default).to.equal(csvData[1][1]);
203
- expect(rows[0].hu).to.equal('Elfogadom');
204
- expect(rows[0].namespace).to.equal(namespaces[0]);
205
- expect(rows[1].default).to.equal(csvData[2][1]);
206
- expect(rows[1].de).to.equal(csvData[2][2]);
207
- expect(rows[1].key).to.equal(csvData[2][0]);
200
+ expect(rows[0].get(options.keyId)).to.equal(csvData[1][0]);
201
+ expect(rows[0].get('pl # with comment')).to.equal(csvData[1][5]);
202
+ expect(rows[0].get('default')).to.equal(csvData[1][1]);
203
+ expect(rows[0].get('hu')).to.equal('Elfogadom');
204
+ expect(rows[0].get('namespace')).to.equal(namespaces[0]);
205
+ expect(rows[1].get('default')).to.equal(csvData[2][1]);
206
+ expect(rows[1].get('de')).to.equal(csvData[2][2]);
207
+ expect(rows[1].get(options.keyId)).to.equal(csvData[2][0]);
208
208
  // this should be already the next namespace
209
- expect(rows[entryLength].namespace).to.equal(namespaces[1]);
209
+ expect(rows[entryLength].get('namespace')).to.equal(namespaces[1]);
210
210
  rimraf.sync(tempFolder.name);
211
211
  done()
212
+ }).catch(err => {
213
+ console.log(err);
214
+ expect(err).to.be.null;
215
+ done()
212
216
  })
213
217
  })
214
218
  } else {
@@ -276,13 +280,13 @@ const tests = [
276
280
  limit: csvData.length - 1
277
281
  }).then(function (rows) {
278
282
  expect(rows).to.have.lengthOf(csvData.length - 1);
279
- expect(rows[0][options.keyId]).to.equal(csvData[1][0]);
280
- expect(rows[0]['pl # with comment']).to.equal(csvData[1][5]);
281
- expect(rows[0].default).to.equal(csvData[1][1]);
282
- expect(rows[0].hu).to.equal('Elfogadom'); // this was not part of the upload and should not be overwrittem
283
- expect(rows[1].default).to.equal(csvData[2][1]);
284
- expect(rows[1].de).to.equal(csvData[2][2]);
285
- expect(rows[1].key).to.equal(csvData[2][0]);
283
+ expect(rows[0].get(options.keyId)).to.equal(csvData[1][0]);
284
+ expect(rows[0].get('pl # with comment')).to.equal(csvData[1][5]);
285
+ expect(rows[0].get('default')).to.equal(csvData[1][1]);
286
+ expect(rows[0].get('hu')).to.equal('Elfogadom'); // this was not part of the upload and should not be overwrittem
287
+ expect(rows[1].get('default')).to.equal(csvData[2][1]);
288
+ expect(rows[1].get('de')).to.equal(csvData[2][2]);
289
+ expect(rows[1].get(options.keyId)).to.equal(csvData[2][0]);
286
290
  rimraf.sync(tempFolder.name);
287
291
  done()
288
292
  })
@@ -577,13 +581,13 @@ const tests = [
577
581
  limit: csvData.length - 1
578
582
  }).then(function (rows) {
579
583
  expect(rows).to.have.lengthOf(csvData.length - 1);
580
- expect(rows[0][options.keyId]).to.equal(csvData[1][0]);
581
- expect(rows[0]['pl # with comment']).to.equal(csvData[1][5]);
582
- expect(rows[0].default).to.equal(csvData[1][1]);
583
- expect(rows[0].hu).to.equal('Elfogadom'); // this was not part of the upload and should not be overwrittem
584
- expect(rows[1].default).to.equal(csvData[2][1]);
585
- expect(rows[1].de).to.equal(csvData[2][2]);
586
- expect(rows[1].key).to.equal(csvData[2][0]);
584
+ expect(rows[0].get(options.keyId)).to.equal(csvData[1][0]);
585
+ expect(rows[0].get('pl # with comment')).to.equal(csvData[1][5]);
586
+ expect(rows[0].get('default')).to.equal(csvData[1][1]);
587
+ expect(rows[0].get('hu')).to.equal('Elfogadom'); // this was not part of the upload and should not be overwrittem
588
+ expect(rows[1].get('default')).to.equal(csvData[2][1]);
589
+ expect(rows[1].get('de')).to.equal(csvData[2][2]);
590
+ expect(rows[1].get(options.keyId)).to.equal(csvData[2][0]);
587
591
  rimraf.sync(tempFolder.name);
588
592
  done()
589
593
  })
@@ -690,13 +694,13 @@ const tests = [
690
694
  limit: csvData.length - 1
691
695
  }).then(function (rows) {
692
696
  expect(rows).to.have.lengthOf(csvData.length - 1);
693
- expect(rows[0][options.keyId]).to.equal(csvData[1][0]);
694
- expect(rows[0]['pl # with comment']).to.equal(csvData[1][5]);
695
- expect(rows[0].default).to.equal(csvData[1][1]);
696
- expect(rows[0].hu).to.equal('Elfogadom'); // this was not part of the upload and should not be overwrittem
697
- expect(rows[1].default).to.equal(csvData[2][1]);
698
- expect(rows[1].de).to.equal(csvData[2][2]);
699
- expect(rows[1].key).to.equal(csvData[2][0]);
697
+ expect(rows[0].get(options.keyId)).to.equal(csvData[1][0]);
698
+ expect(rows[0].get('pl # with comment')).to.equal(csvData[1][5]);
699
+ expect(rows[0].get('default')).to.equal(csvData[1][1]);
700
+ expect(rows[0].get('hu')).to.equal('Elfogadom'); // this was not part of the upload and should not be overwrittem
701
+ expect(rows[1].get('default')).to.equal(csvData[2][1]);
702
+ expect(rows[1].get('de')).to.equal(csvData[2][2]);
703
+ expect(rows[1].get(options.keyId)).to.equal(csvData[2][0]);
700
704
  rimraf.sync(tempFolder.name);
701
705
  done()
702
706
  })
@@ -1,14 +0,0 @@
1
- {
2
- "comment": "This is my the spreadsheet translation sync project at https://console.developers.google.com",
3
-
4
- "type": "service_account",
5
- "project_id": "seismic-hexagon-171311",
6
- "private_key_id": "b2efa9eb154ca1db144e2028e7a28fd84a298b38",
7
- "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDIvRemip3OQKLi\nmsKTwksSaoH/BqyvcaLYee2EwKGjPO8or4pvgcx/yys2ZuHnm3r3MI4WFuUEoiji\nj+DhI2+X2DEa3DyUbsv2lrOQjL0WQ/z6+8aAezbcDk/FwDefkvrpDs8EQoHzlylI\nBk9Junu/R/HCS8Ul0fbRLsVd5gsz+N6JCxiKxxD0iBhLjvQVLc1vEqbQMXU66W2w\nGvCGiozNrvfX+OTzrY4Xig2YnvOwT69ixNT71ffF2li43bb6Efm+N2acx1ouRmKt\nSMguKsbOs1MwF31AfUwlsgqyHt5i9QnC4cYuoARd+sxNS3rmiglvcNyHfpN1S0r9\n6rGSTvuzAgMBAAECggEACdbW5qD4IvvURnYrRmUC5/6t3rgb7vRYQb/lzmV6WfV3\ni+kUYh3P58Da6xPRsj5sbkEd6papsaq/HuFj6bUicaQ1Plzmk6hdbMZ7O7RIsMnM\nh+k4x0oCz0UP6bn5UzC4BeqZANtZSLBmluZHeaFfeNAIDHivsghftP+6L3DCQmqz\n4b+E0Dc0SphsiRjcr5E04L0srVs7mcioe53/QIDYLlU+CgqPK1eVuE0EpE/K1qTS\nkBrX2DkdD4ZwgWCFH8JtiZxudEuDFsXdKeeZnAIaXnFWMUfdkAfSqU0bVecQhzwJ\nKoMD4tBRrRy7OKFg/r1X6siVLwGMgEMCUfF95UjKOQKBgQDllqoQPCfy3EQcxWpI\nsPnY33FGTBkzG+DiVPk8nFg/BmSjGLK8o27f/C13dZQYk+npy2idBRLivtdTBHnX\n40D9IbpPY2znHZG9v/55aM/Ak4IToK5LcYRO/0DSgP3d75DyK4c2E/OHeyZZQ5re\nbLAa5kaEexq+AKwwgMUplODXuwKBgQDf1M3+Iq1Tjacy3JI71Xjg9/44B3YFdQKc\nHhaAFQAQ8S73aUbXjUEGsALhPuysAlMruXf400ccqLG/jIqQCN2me7SDMOBoaVIf\nvs7OJxMwtleymsdRO2WiyqM/jgsz/RzMUyEm2bcOVyh70rmjdKb2kRhNzPmiKfKd\nrSk96JuAaQKBgQCd9XT4SospuoZaTTD85lZf8gubVAwab9nfnpsEKoVEh3+B/yIq\nSz+jd3tIr2q/JjVPl5VYQv9lZev7wB6cfExgXG405LEuqdd197x7h7M4ScS7AFgq\nq+Vd/Jx6uCjyIztUgsI6YKuy0PXngG3zDlBzkW9/Aru5cfkGPfJo03eGtQKBgQCB\nmhuIShXoKj4kshiuMoXiZVW6pX9N25meQcGveflm1e/m6/tL9gsvzxxrxd6OHm6A\nA80Ws2cajgcVrU/R5IBK1JgJ2nQloM4feYEPVwrCiuIulXZRb74Cy6hItXuBJ87T\n9FJex3M6B3d2cuZwYawHWq4i5kuo/PbxG4Gkyhm3+QKBgCB7yQy0vS+uqg9rPqtg\nWdgGTP2DC39swN4hiIkbXKk4kYknnTxuJa6nEpg5gRtujOuTzLsLgSyL9dz+G51t\nFWAbX2E3+DuAmqetY703lPe8Q4CyZV3Jr6sG1astndJ/X0qvcXII5YC03HlpoN76\nhc5B09JoVtUMBIUiJne4RxDS\n-----END PRIVATE KEY-----\n",
8
- "client_email": "service@seismic-hexagon-171311.iam.gserviceaccount.com",
9
- "client_id": "116216769033616725253",
10
- "auth_uri": "https://accounts.google.com/o/oauth2/auth",
11
- "token_uri": "https://accounts.google.com/o/oauth2/token",
12
- "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
13
- "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/service%40seismic-hexagon-171311.iam.gserviceaccount.com"
14
- }