@sap/cds-dk 7.5.0 → 7.6.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.
Files changed (67) hide show
  1. package/bin/add.js +28 -24
  2. package/bin/bind.js +59 -32
  3. package/bin/build.js +1 -1
  4. package/bin/cds.js +1 -1
  5. package/bin/env.js +1 -6
  6. package/bin/init.js +7 -3
  7. package/bin/logout.js +2 -2
  8. package/bin/migrate.js +1 -1
  9. package/bin/version.js +1 -0
  10. package/bin/watch.js +8 -1
  11. package/lib/bind/bindingUtil.js +14 -20
  12. package/lib/bind/cfKeyProvider.js +9 -11
  13. package/lib/bind/index.js +4 -2
  14. package/lib/build/buildTaskFactory.js +31 -20
  15. package/lib/build/constants.js +9 -8
  16. package/lib/build/provider/featureToggleBuildPlugin.js +1 -7
  17. package/lib/build/provider/hana/index.js +44 -9
  18. package/lib/build/provider/internalBuildTaskProvider.js +7 -5
  19. package/lib/build/provider/java/index.js +0 -6
  20. package/lib/build/provider/mtx-extension/index.js +2 -1
  21. package/lib/build/provider/mtx-sidecar/index.js +5 -0
  22. package/lib/build/provider/nodejs/index.js +32 -47
  23. package/lib/build/util.js +134 -33
  24. package/lib/compile/openapi/csdl2openapi.js +1 -1
  25. package/lib/deploy/to-hana/hana.js +1 -1
  26. package/lib/env/schema.js +157 -17
  27. package/lib/env/schemas/cds-rc.js +118 -0
  28. package/lib/init/constants.js +1 -1
  29. package/lib/init/merge.js +2 -1
  30. package/lib/init/template/application-logging/index.js +1 -2
  31. package/lib/init/template/approuter/files/mta.yaml.hbs +2 -0
  32. package/lib/init/template/approuter/files/package.json +1 -1
  33. package/lib/init/template/approuter/files/values.yaml.hbs +8 -1
  34. package/lib/init/template/hana/files/undeploy.json +3 -2
  35. package/lib/init/template/helm/files/chart/templates/NOTES.txt +2 -0
  36. package/lib/init/template/helm/files/chart/templates/_deployment_helpers.tpl +10 -2
  37. package/lib/init/template/helm/files/content-deployment/Chart.yaml +1 -1
  38. package/lib/init/template/helm/files/content-deployment/templates/_helpers.tpl +6 -5
  39. package/lib/init/template/helm/files/content-deployment/values.yaml +1 -1
  40. package/lib/init/template/helm/files/service-instance/Chart.yaml +1 -1
  41. package/lib/init/template/helm/files/service-instance/templates/_helpers.tpl +6 -5
  42. package/lib/init/template/helm/files/service-instance/values.yaml +1 -1
  43. package/lib/init/template/helm/files/web-application/Chart.yaml +1 -1
  44. package/lib/init/template/helm/files/web-application/templates/_api.tpl +6 -5
  45. package/lib/init/template/helm/files/web-application/values.yaml +1 -1
  46. package/lib/init/template/helm/index.js +1 -12
  47. package/lib/init/template/helm-unified-runtime/index.js +3 -2
  48. package/lib/init/template/html5-repo/files/values.yaml.hbs +9 -0
  49. package/lib/init/template/multitenancy/files/package.sidecar.json.hbs +1 -2
  50. package/lib/init/template/multitenancy/index.js +3 -2
  51. package/lib/init/template/xsuaa/files/package.json.hbs +1 -2
  52. package/lib/mtx/activate.js +1 -1
  53. package/lib/mtx/auth_manager.js +4 -2
  54. package/lib/mtx/download_legacy.js +1 -1
  55. package/lib/mtx/extend.js +2 -2
  56. package/lib/mtx/pull.js +1 -1
  57. package/lib/mtx/push.js +1 -1
  58. package/lib/mtx/settings_manager.js +77 -26
  59. package/lib/mtx/tenant_action.js +1 -1
  60. package/lib/mtx/util/legacy_ext.js +0 -1
  61. package/lib/util/axios.js +66 -0
  62. package/lib/util/cf.js +15 -10
  63. package/npm-shrinkwrap.json +361 -167
  64. package/package.json +1 -1
  65. package/lib/env/schemas/cds-rc.json +0 -101
  66. package/lib/init/template/helm-unified-runtime/lib/build.js +0 -0
  67. package/lib/util/pruneAxiosErrors.js +0 -56
@@ -1,8 +1,7 @@
1
1
  {
2
2
  {{#isNodejs}}
3
3
  "dependencies": {
4
- "@sap/xssec": "^3",
5
- "passport": "^0"
4
+ "@sap/xssec": "^3"
6
5
  },
7
6
  "cds": {
8
7
  {{/isNodejs}}
@@ -5,7 +5,7 @@ const axios = require('axios');
5
5
  const cds = require('../cds');
6
6
  const AuthManager = require('./auth_manager');
7
7
 
8
- require('../util/pruneAxiosErrors');
8
+ require('../util/axios').pruneErrors();
9
9
  const { collectFileContent, collectFiles, toUnixPath } = require('./util/fs');
10
10
  const { getMessage } = require('./util/logging');
11
11
  const { handleHttpError } = require('./util/errors');
@@ -4,7 +4,7 @@ const cds = require('../cds');
4
4
  const { SettingsManager } = require('./settings_manager');
5
5
  const { ParamCollection } = require('./params');
6
6
 
7
- require('../util/pruneAxiosErrors');
7
+ require('../util/axios').pruneErrors();
8
8
  const { getMessage } = require('./util/logging');
9
9
 
10
10
  const DEBUG = cds.debug('cli');
@@ -127,8 +127,10 @@ module.exports = class AuthManager {
127
127
  static async logout(paramValues) {
128
128
  SettingsManager.init();
129
129
  const params = new ParamCollection(paramValues);
130
+ await SettingsManager.setKeytar(params, true);
130
131
  if (params.get('clearInvalid')) {
131
- await SettingsManager.deleteInvalid();
132
+ await SettingsManager.deleteInvalidSettings();
133
+ await SettingsManager.deleteInvalidTokens();
132
134
  } else {
133
135
  await SettingsManager.deleteToken(params);
134
136
  if (params.get('deleteSettings')) {
@@ -4,7 +4,7 @@ const axios = require('axios');
4
4
 
5
5
  const { login } = require('./auth_manager');
6
6
 
7
- require('../util/pruneAxiosErrors');
7
+ require('../util/axios').pruneErrors();
8
8
  const { handleHttpError } = require('./util/errors');
9
9
 
10
10
  const DOWNLOAD_FILE_NAME = 'migrated_projects.tgz'; // TODO try to add tenant later, if possible
package/lib/mtx/extend.js CHANGED
@@ -4,7 +4,7 @@ const axios = require('axios');
4
4
 
5
5
  const AuthManager = require('./auth_manager');
6
6
 
7
- require('../util/pruneAxiosErrors');
7
+ require('../util/axios').pruneErrors();
8
8
  const { normalizePath, isParent } = require('./util/fs');
9
9
  const Question = require('./util/question');
10
10
  const { getMessage } = require('./util/logging');
@@ -163,7 +163,7 @@ module.exports = class Extend {
163
163
  }
164
164
 
165
165
  static promptTemplateOverwrite() {
166
- return Question.askBooleanQuestion(`This will overwrite extisting templates in folder ${TEMPLATE_FOLDER}. Continue (yN)? `);
166
+ return Question.askBooleanQuestion(`This will overwrite existing templates in folder ${TEMPLATE_FOLDER}. Continue (yN)? `);
167
167
  }
168
168
 
169
169
  static _logWrittenFile(file) {
package/lib/mtx/pull.js CHANGED
@@ -7,7 +7,7 @@ const axios = require('axios');
7
7
 
8
8
  const { login } = require('./auth_manager');
9
9
 
10
- require('../util/pruneAxiosErrors');
10
+ require('../util/axios').pruneErrors();
11
11
  const { getMessage } = require('./util/logging');
12
12
  const { handleHttpError } = require('./util/errors');
13
13
 
package/lib/mtx/push.js CHANGED
@@ -6,7 +6,7 @@ const axios = require('axios');
6
6
  const { login } = require('./auth_manager');
7
7
  const buildSystem = require('../build')
8
8
 
9
- require('../util/pruneAxiosErrors');
9
+ require('../util/axios').pruneErrors();
10
10
  const { getMessage } = require('./util/logging');
11
11
  const { schemaRegex } = require('./util/urls');
12
12
  const { handleHttpError } = require('./util/errors');
@@ -50,6 +50,7 @@ const CONFIG = {
50
50
  };
51
51
 
52
52
  let keytar;
53
+ let keytarDisabled;
53
54
 
54
55
  function other(tokenStorage) {
55
56
  if (!tokenStorage) {
@@ -126,6 +127,14 @@ function getKeyringAccountName(params) {
126
127
  return `${params.get('appUrl')}|${params.get('subdomain')}`;
127
128
  }
128
129
 
130
+ function getAccountUrl(account) {
131
+ return account.includes('|') ? account.split('|')[0] : null;
132
+ }
133
+
134
+ function getAccountSubdomain(account) {
135
+ return account.includes('|') ? account.split('|')[1] : null;
136
+ }
137
+
129
138
  function logConfigPaths() {
130
139
  DEBUG?.(`Settings are stored in ${CONFIG.paths.settings}`);
131
140
  DEBUG?.(`Authentication data is stored in ${CONFIG.paths.auth} (${TOKEN_STORAGE_DESC.plain}) or ${CONFIG.keyringDesignation} (${TOKEN_STORAGE_DESC.keyring})`);
@@ -145,6 +154,8 @@ class SettingsManager {
145
154
 
146
155
  static init() {
147
156
  logConfigPaths();
157
+ keytar = undefined;
158
+ keytarDisabled = undefined;
148
159
  }
149
160
 
150
161
  static get config() {
@@ -175,7 +186,7 @@ class SettingsManager {
175
186
  if (params.get('clearOtherTokenStorage')) {
176
187
  // Delete token from the other storage.
177
188
  params.delete('clearOtherTokenStorage');
178
- await this.deleteToken(params, true);
189
+ await this.deleteToken(params, { fromOtherStorage: true });
179
190
  }
180
191
 
181
192
  if (params.get('tokenStorage') === 'plain') {
@@ -259,7 +270,7 @@ class SettingsManager {
259
270
  }
260
271
 
261
272
  static async setKeytar(params, logout = false) {
262
- if (params.get('skipToken')) {
273
+ if (keytar || keytarDisabled || params.get('skipToken')) {
263
274
  return;
264
275
  }
265
276
  if (isBas()) {
@@ -273,7 +284,7 @@ class SettingsManager {
273
284
  if (keytar) {
274
285
  return;
275
286
  }
276
- // keytar missing
287
+ keytarDisabled = true;
277
288
 
278
289
  if (params.get('tokenStorage') === 'keyring' && params.get('saveData')) {
279
290
  // Explicit login w/o plain-text storage enabled
@@ -318,7 +329,19 @@ class SettingsManager {
318
329
  throw 'Passcode cannot be empty';
319
330
  }
320
331
  }
321
- function setRelevantAuth(auth) {
332
+ async function addAuthSettings() {
333
+ function removeObsoleteToken() {
334
+ if (params.get('renewLogin') || params.get('tokenExpirationDate') <= Date.now()) {
335
+ DEBUG?.((params.get('renewLogin') ? 'Renewing' : 'Refreshing expired') + ' authentication token');
336
+ params.delete('token');
337
+ params.delete('tokenExpirationDate');
338
+ }
339
+ }
340
+
341
+ const auth = {
342
+ plain: await SettingsManager._loadAuthFromFile(params.get('appUrl'), params.get('subdomain')),
343
+ keyring: await SettingsManager._loadAuthFromKeyring(params.get('appUrl'), params.get('subdomain'))
344
+ };
322
345
  if (!(auth.keyring.has('tokenStorage') || auth.plain.has('tokenStorage'))) {
323
346
  // Saved auth data not present.
324
347
  return;
@@ -348,12 +371,8 @@ class SettingsManager {
348
371
 
349
372
  params.merge(auth[storage]);
350
373
  }
351
- }
352
- function removeObsoleteToken() {
353
- if (params.get('renewLogin') || params.get('tokenExpirationDate') <= Date.now()) {
354
- DEBUG?.((params.get('renewLogin') ? 'Renewing' : 'Refreshing expired') + ' authentication token');
355
- params.delete('token');
356
- params.delete('tokenExpirationDate');
374
+ if (params.has('token')) {
375
+ removeObsoleteToken();
357
376
  }
358
377
  }
359
378
 
@@ -387,21 +406,13 @@ class SettingsManager {
387
406
  }
388
407
  }
389
408
 
390
- const auth = {
391
- plain: await this._loadAuthFromFile(params.get('appUrl'), params.get('subdomain')),
392
- keyring: await this._loadAuthFromKeyring(params.get('appUrl'), params.get('subdomain'))
393
- };
394
-
395
- setRelevantAuth(auth);
396
- if (params.has('token')) {
397
- removeObsoleteToken();
398
- }
409
+ await addAuthSettings();
399
410
  if (!logout && !params.has('token') && !params.has('refreshToken') && !params.has('passcode') && !params.has('clientsecret') && !params.has('key')) {
400
411
  await addPasscode();
401
412
  }
402
413
  }
403
414
 
404
- static async deleteToken(params, fromOtherStorage = false) {
415
+ static async deleteToken(params, { fromOtherStorage = false, invalid = false } = {}) {
405
416
  const allParams = params.clone();
406
417
  await this.loadAndMergeSettings(allParams, true);
407
418
 
@@ -409,11 +420,13 @@ class SettingsManager {
409
420
  let fromStorage = fromOtherStorage && other(params.get('tokenStorage'));
410
421
  const deleteBoth = !fromStorage && allParams.get('tokenStorage') && allParams.get('clearOtherTokenStorage');
411
422
 
412
- DEBUG?.(`Deleting authentication data${fromOtherStorage ? ' from other storage' : ''} for ${target}`);
423
+ DEBUG?.(`Deleting${invalid ? ' invalid' : ''} authentication data${fromOtherStorage ? ' from other storage' : ''} for ${target}`);
413
424
 
414
425
  fromStorage = fromStorage || allParams.get('tokenStorage');
415
426
  if (!fromStorage) {
416
- console.log('Failed to delete authentication data: none found for', target);
427
+ if (!invalid) {
428
+ console.log('Failed to delete authentication data: none found for', target);
429
+ }
417
430
  return;
418
431
  }
419
432
 
@@ -430,7 +443,7 @@ class SettingsManager {
430
443
  await this._saveToFile(projectFolder, null);
431
444
  }
432
445
 
433
- static async deleteInvalid() {
446
+ static async deleteInvalidSettings() {
434
447
  const settingsByFolder = await this._loadFromFile(undefined);
435
448
  const deletionFolders = [];
436
449
  const deletionUrlsAndSubdomains = new Set();
@@ -451,15 +464,33 @@ class SettingsManager {
451
464
  const subdomain = urlAndSubdomain.replace(/.*\|/, '');
452
465
  const urlReference = Object.values(settingsByFolder).find(settings => settings.appUrl === appUrl && settings.subdomain === subdomain);
453
466
  if (!urlReference) {
454
- await this.deleteToken(new ParamCollection({ appUrl, subdomain }));
467
+ await this.deleteToken(new ParamCollection({ appUrl, subdomain }), { invalid: true });
455
468
  }
456
469
  }
457
470
  console.log('Deleted settings for nonexistent project folders:', deletionFolders.map(folder => ' ' + folder));
458
471
  } else {
459
- console.log('All settings seem valid');
472
+ console.log('All saved project folders seem valid');
460
473
  }
461
474
  }
462
475
 
476
+ static async deleteInvalidTokens() {
477
+ async function deleteFrom(allAuth) {
478
+ for (const [appUrl, authForUrl = {}] of Object.entries(allAuth)) {
479
+ for (const [subdomain, auth] of Object.entries(authForUrl)) {
480
+ if (!auth.token) {
481
+ continue;
482
+ }
483
+ if (auth.tokenExpirationDate <= Date.now()) {
484
+ await SettingsManager.deleteToken(new ParamCollection({ appUrl, subdomain }), { invalid: true });
485
+ }
486
+ }
487
+ }
488
+ }
489
+
490
+ await deleteFrom(await this._loadAuthFromFile(undefined, undefined));
491
+ await deleteFrom(await this._loadAllAuthFromKeyring());
492
+ }
493
+
463
494
  static async _saveToFile(projectFolder, paramValues) {
464
495
  const paramValuesByFolder = await this._loadFromFile(undefined);
465
496
  if (paramValues !== null) {
@@ -559,7 +590,7 @@ class SettingsManager {
559
590
  try {
560
591
  allAuth = JSON.parse((await fs.promises.readFile(CONFIG.paths.auth)).toString());
561
592
  } catch (err) {
562
- DEBUG?.('Empty authentication-data file');
593
+ DEBUG?.('File contains invalid saved auth');
563
594
  return emptyData(all);
564
595
  }
565
596
 
@@ -613,6 +644,26 @@ class SettingsManager {
613
644
  return new ParamCollection(auth);
614
645
  }
615
646
 
647
+ static async _loadAllAuthFromKeyring() {
648
+ if (!keytar) {
649
+ return {};
650
+ }
651
+
652
+ return (await keytar.findCredentials(MTX_FULLY_QUALIFIED))
653
+ .reduce((result, { account, password: authString }) => {
654
+ const appUrl = getAccountUrl(account);
655
+ const subdomain = getAccountSubdomain(account);
656
+ if (appUrl && subdomain) {
657
+ try {
658
+ (result[appUrl] ??= {})[subdomain] = JSON.parse(authString);
659
+ } catch (error) {
660
+ throw new Error(`${capitalize(TOKEN_STORAGE_DESC.keyring)} contains invalid saved auth for URL ${appUrl} and subdomain ${subdomain}`);
661
+ }
662
+ }
663
+ return result;
664
+ }, {});
665
+ }
666
+
616
667
  }
617
668
 
618
669
  module.exports = {
@@ -2,7 +2,7 @@ const cds = require('../cds');
2
2
  const { ParamCollection } = require('./params');
3
3
  const AuthManager = require('./auth_manager');
4
4
 
5
- require('../util/pruneAxiosErrors');
5
+ require('../util/axios').pruneErrors();
6
6
  const { getMessage } = require('./util/logging');
7
7
  const { capitalize } = require('./util/strings');
8
8
  const { handleHttpError } = require('./util/errors');
@@ -2,7 +2,6 @@ const path = require('path');
2
2
  const fs = require('fs').promises;
3
3
 
4
4
  const { concatUrls } = require('./urls');
5
- require('../../util/pruneAxiosErrors');
6
5
 
7
6
  const MTX_URL_PATH_PREFIX = 'mtx/v1/model/';
8
7
 
@@ -0,0 +1,66 @@
1
+ module.exports = {
2
+ pruneErrors
3
+ };
4
+
5
+ function pruneErrors() {
6
+ function stacklessError(message) {
7
+ const { stackTraceLimit } = Error;
8
+ Error.stackTraceLimit = 0;
9
+ const error = new Error(message);
10
+ Error.stackTraceLimit = stackTraceLimit;
11
+ return error;
12
+ }
13
+
14
+ require('axios').interceptors.response.use(
15
+ response => response,
16
+ axError => {
17
+ const { inspect } = require('util');
18
+ const { url, method } = axError.config ?? {};
19
+ const { errno, code, response = {}, message: axMessage } = axError;
20
+ const prefix = (url && method ? `${method.toUpperCase()} ${url}` : 'Request') + ' failed';
21
+
22
+ let error;
23
+ if (errno) {
24
+ // System error during request.
25
+ const message = prefix
26
+ + `: ${errno} ${code}`
27
+ + (/\b(ENOTFOUND|EAI_AGAIN)\b/.test(code) ? `. Make sure the URL is correct and the server is running.` : '');
28
+ error = stacklessError(message);
29
+ } else {
30
+ // Error after request has been made.
31
+ const { status, statusText } = response;
32
+ let { data } = response;
33
+ if (Buffer.isBuffer(data)) {
34
+ data = data.toString();
35
+ try {
36
+ data = JSON.parse(data);
37
+ } catch (error) { /* unexpected; data will be included in error below */
38
+ }
39
+ }
40
+ const inspectData = data && !data.error_description;
41
+ const reason = !inspectData && data?.error /* RFC 6749 */ && (typeof data.error === 'string' ? data.error : inspect(data.error));
42
+ const details = inspectData ? inspect(data) : data?.error_description /* RFC 6749 */;
43
+ const message = prefix + (
44
+ status || statusText || reason || details
45
+ ? (status || statusText ? ':' : '') +
46
+ (status ? ` ${status}` : '') +
47
+ (statusText ? ` ${statusText}` : '') +
48
+ (reason ? `. ${reason}` : '') +
49
+ (details ? `. Details: ${details}` : '')
50
+ : (axMessage ? `: ${axMessage}` : '')
51
+ );
52
+
53
+ error = stacklessError(message);
54
+ if (status) {
55
+ error.status = status;
56
+ }
57
+ }
58
+
59
+ if (require('../').debug('req')) {
60
+ error.cause = axError;
61
+ }
62
+ return Promise.reject(error);
63
+ }
64
+ );
65
+ return module.exports;
66
+ }
package/lib/util/cf.js CHANGED
@@ -116,7 +116,7 @@ class CfUtil {
116
116
  if (config) {
117
117
  return {
118
118
  apiEndpoint: this._extract(config.Target, /\s*(.+)\s*/, `CF API endpoint is missing. Use 'cf login' to login.`),
119
- org: this._extract(config.OrganizationFields.Name, /\s*(.+)\s*/, `CF org is missing. Use 'cf target -o <ORG> to specify.`),
119
+ org: this._extract(config.OrganizationFields.Name, /\s*(.+)\s*/, `CF org is missing. Use 'cf target -o <ORG>' to specify.`),
120
120
  space: this._extract(config.SpaceFields.Name, /\s*(.+)\s*/, `CF space is missing. Use 'cf target -s <SPACE>' to specify.`)
121
121
  }
122
122
  }
@@ -131,7 +131,7 @@ class CfUtil {
131
131
  return {
132
132
  apiEndpoint: this._extract(result.stdout, /api endpoint\s*:\s*([^\s]+)/i, `CF API endpoint is missing. Use 'cf login' to login.`),
133
133
  user: this._extract(result.stdout, /user\s*:\s*(.+)/i, `CF user is missing. Use 'cf login' to login.`),
134
- org: this._extract(result.stdout, /org\s*:\s*(.+)/i, `CF org is missing. Use 'cf target -o <ORG> to specify.`),
134
+ org: this._extract(result.stdout, /org\s*:\s*(.+)/i, `CF org is missing. Use 'cf target -o <ORG>' to specify.`),
135
135
  space: this._extract(result.stdout, /space\s*:\s*(.+)/i, `CF space is missing. Use 'cf target -s <SPACE>' to specify.`),
136
136
  };
137
137
  }
@@ -189,6 +189,14 @@ Deployment will stop in the near future for Cloud Foundry client versions < ${CF
189
189
  return this.spaceInfo;
190
190
  }
191
191
 
192
+ async getServices(app) {
193
+ const { spaceGuid: space_guids, orgGuid: organization_guids } = await this.getCfSpaceInfo();
194
+ const apps = await this._cfRequest('/v3/apps', { space_guids, organization_guids, names: app });
195
+ const guid = apps.resources[0].guid;
196
+ const env = await this._cfRequest('/v3/apps/'+guid+'/env', { space_guids, organization_guids, names: app });
197
+ return Object.values(env.system_env_json.VCAP_SERVICES).flat();
198
+ }
199
+
192
200
  async getService(serviceName, showMessage = true) {
193
201
  showMessage && console.log(`Getting service ${bold(serviceName)}`);
194
202
  const spaceInfo = await this.getCfSpaceInfo();
@@ -323,15 +331,14 @@ Deployment will stop in the near future for Cloud Foundry client versions < ${CF
323
331
  }
324
332
 
325
333
 
326
- async getOrCreateServiceKey(serviceInstance, serviceKeyName) {
327
-
334
+ async getOrCreateServiceKey(serviceInstance, serviceKeyName, parameters, { silent = false, create = true } = {}) {
328
335
  const serviceKey = await this.getServiceKey(serviceInstance, serviceKeyName, false);
329
- if (serviceKey) {
330
- console.log(`Getting service key ${bold(serviceKeyName)}`);
336
+ if (serviceKey || !create) {
337
+ if (serviceKey && !silent) console.log(`Getting service key ${bold(serviceKeyName)}`);
331
338
  return serviceKey;
332
339
  }
333
340
 
334
- console.log(`Creating service key ${bold(serviceKeyName)} - please be patient...`);
341
+ if (!silent) console.log(`Creating service key ${bold(serviceKeyName)} - please be patient...`);
335
342
 
336
343
  const body = {
337
344
  type: 'key',
@@ -343,9 +350,7 @@ Deployment will stop in the near future for Cloud Foundry client versions < ${CF
343
350
  }
344
351
  }
345
352
  },
346
- parameters: {
347
- permissions: 'development'
348
- }
353
+ parameters
349
354
  }
350
355
 
351
356
  const postResult = await this._cfRequest('/v3/service_credential_bindings', undefined, body);