@sap/cds-dk 6.0.3 → 6.1.2

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 (105) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/bin/add.js +29 -16
  3. package/bin/compile/index.js +7 -2
  4. package/bin/fix-redirects.js +4 -2
  5. package/bin/init.js +5 -0
  6. package/bin/login.js +20 -4
  7. package/bin/pull.js +58 -0
  8. package/bin/push.js +52 -0
  9. package/bin/subscribe.js +45 -0
  10. package/lib/bind/bindingUtil.js +1 -2
  11. package/lib/bind/cfKeyProvider.js +3 -3
  12. package/lib/cds.js +2 -3
  13. package/lib/client/activate.js +17 -16
  14. package/lib/client/auth_manager.js +10 -5
  15. package/lib/client/base_command.js +4 -6
  16. package/lib/client/extend.js +3 -2
  17. package/lib/client/helper/cf_helper.js +1 -0
  18. package/lib/client/helper/errors.js +11 -0
  19. package/lib/client/helper/logging.js +3 -10
  20. package/lib/client/helper/url_helper.js +7 -3
  21. package/lib/client/index.js +1 -1
  22. package/lib/client/params.js +9 -6
  23. package/lib/client/pull.js +48 -0
  24. package/lib/client/push.js +45 -0
  25. package/lib/client/settings_manager.js +81 -27
  26. package/lib/compile/openapi/csdl2openapi.js +135 -163
  27. package/lib/compile/openapi/index.js +0 -1
  28. package/lib/import/common.js +9 -1
  29. package/lib/import/odata/metadataconverter/MetadataConverter.js +17 -4
  30. package/lib/import/odata/v2parser.js +50 -26
  31. package/lib/import/odata/v4parser.js +110 -29
  32. package/lib/import/openapi/importOpenAPI.js +3 -3
  33. package/lib/init/constants.js +9 -3
  34. package/lib/init/template/_merging/registry-mta.js +284 -0
  35. package/lib/init/template/approuter/files/xs-app.json.hbs +7 -7
  36. package/lib/init/template/approuter/index.js +22 -32
  37. package/lib/init/template/auditlog/index.js +7 -9
  38. package/lib/init/template/data/index.js +2 -4
  39. package/lib/init/template/extensibility/files/cds.package.json +7 -0
  40. package/lib/init/template/extensibility/files/cds.package.json.hbs +9 -0
  41. package/lib/init/template/extensibility/files/xs-security.json.hbs +26 -0
  42. package/lib/init/template/extensibility/index.js +48 -0
  43. package/lib/init/template/hana/files/java/cdsrc.json.hbs +1 -1
  44. package/lib/init/template/hana/files/java/profile.cdsrc.json.hbs +2 -2
  45. package/lib/init/template/hana/files/mta.yaml.hbs +10 -0
  46. package/lib/init/template/hana/files/nodejs/cds.package.json.hbs +1 -1
  47. package/lib/init/template/hana/files/nodejs/cds.profile.package.json.hbs +2 -2
  48. package/lib/init/template/hana/index.js +76 -63
  49. package/lib/init/template/kibana-logging/index.js +31 -37
  50. package/lib/init/template/lint/files/.eslint/rules/no-entity-moo.js +14 -14
  51. package/lib/init/template/mta/files/mta.yaml.hbs +1 -1
  52. package/lib/init/template/mta/index.js +13 -7
  53. package/lib/init/template/mtx/index.js +2 -142
  54. package/lib/init/template/multitenancy/files/cds.package.json +7 -0
  55. package/lib/init/template/multitenancy/files/cds.package.json.hbs +9 -0
  56. package/lib/init/template/{mtx → multitenancy}/files/dependencies.package.json +1 -1
  57. package/lib/init/template/multitenancy/files/java/.cdsrc.json +19 -0
  58. package/lib/init/template/multitenancy/files/java/package.json +31 -0
  59. package/lib/init/template/{mtx → multitenancy}/files/mta.yaml.hbs +13 -6
  60. package/lib/init/template/multitenancy/files/xs-security.json.hbs +18 -0
  61. package/lib/init/template/multitenancy/index.js +109 -0
  62. package/lib/init/template/nodejs/files/package.json.hbs +3 -0
  63. package/lib/init/template/templateBase.js +2 -1
  64. package/lib/init/template/toggles/files/cds.package.json +7 -0
  65. package/lib/init/template/toggles/files/cds.package.json.hbs +9 -0
  66. package/lib/init/template/toggles/index.js +24 -0
  67. package/lib/init/template/xsuaa/files/mta.yaml.hbs +10 -0
  68. package/lib/init/template/xsuaa/index.js +44 -33
  69. package/lib/init/util/projectReader.js +12 -7
  70. package/lib/init/util/templateUtil.js +113 -111
  71. package/lib/k8s/ChartAdder.js +13 -3
  72. package/lib/k8s/ChartBuilder.js +6 -0
  73. package/lib/k8s/buildModel.js +1 -3
  74. package/lib/k8s/connectivity/index.js +5 -5
  75. package/lib/k8s/deployment/index.js +2 -4
  76. package/lib/k8s/deployment/subchart/Chart.yaml +1 -1
  77. package/lib/k8s/deployment/subchart/templates/_helpers.tpl +22 -11
  78. package/lib/k8s/deployment/subchart/templates/deployment.yaml +12 -5
  79. package/lib/k8s/deployment/subchart/templates/network-policy.yaml +3 -1
  80. package/lib/k8s/deployment/subchart/templates/pod-disruption-budget.yaml +1 -1
  81. package/lib/k8s/deployment/subchart/values.md +812 -0
  82. package/lib/k8s/deployment/subchart/values.schema.json +125 -30
  83. package/lib/k8s/deployment/subchart/values.yaml +7 -3
  84. package/lib/k8s/hana-deployer/index.js +10 -0
  85. package/lib/k8s/hana-deployer/templates/hana-deployer-job.yaml +8 -0
  86. package/lib/k8s/html5-apps-deployer/index.js +10 -0
  87. package/lib/k8s/html5-apps-deployer/templates/html5-apps-deployer-job.yaml +8 -0
  88. package/lib/k8s/index.js +2 -2
  89. package/lib/k8s/resources/helm/chart/schemas/values.root.schema.json +11 -0
  90. package/lib/k8s/resources/helm/chart/schemas/values.schema.json +479 -0
  91. package/lib/k8s/resources/helm/chart/templates/_deployment_helpers.tpl +1 -159
  92. package/lib/k8s/resources/helm/chart/templates/_helpers.tpl +0 -33
  93. package/lib/k8s/resources/helm/chart/templates/_sapcp_helpers.tpl +1 -1
  94. package/lib/lint/Linter.js +155 -158
  95. package/lib/lint/io.js +12 -5
  96. package/npm-shrinkwrap.json +1052 -135
  97. package/package.json +5 -4
  98. package/lib/init/template/_merging/existences.js +0 -297
  99. package/lib/init/template/mtx/files/cds.package.json +0 -12
  100. package/lib/init/template/mtx/files/cds.package.json.hbs +0 -14
  101. package/lib/init/template/mtx/files/java/.cdsrc.json +0 -17
  102. package/lib/init/template/mtx/files/java/package.json +0 -43
  103. package/lib/init/template/mtx/files/java/server.js +0 -30
  104. package/lib/init/template/mtx/files/xs-security.json.hbs +0 -48
  105. package/lib/lint/.eslintrc.js +0 -3
@@ -7,6 +7,7 @@ const BaseCommand = require('./base_command');
7
7
  const { normalizePath, isParent } = require('./helper/fs_helper');
8
8
  const Logger = require('./helper/logging');
9
9
  const Question = require('./helper/question');
10
+ const { CliError } = require('./helper/errors');
10
11
 
11
12
  const COMMAND_URL_SUFFIX = 'content';
12
13
  const CUSTOM_FILE_FOLDERS = ['wsdl'];
@@ -71,7 +72,7 @@ module.exports = class Extend extends BaseCommand {
71
72
 
72
73
  static async run(paramValues) {
73
74
  try {
74
- const params = await AuthManager.loginAndUpdateSettings(paramValues);
75
+ const params = await AuthManager.login(paramValues);
75
76
 
76
77
  if (process.env.DEBUG) {
77
78
  Logger.log(`\n\nCreating extension project with parameters ${JSON.stringify({
@@ -88,7 +89,7 @@ module.exports = class Extend extends BaseCommand {
88
89
 
89
90
  const runOnExistingProject = await Extend.checkProject(params.get('projectFolder'));
90
91
  if (runOnExistingProject && !(params.get('force') || params.get('templates'))) {
91
- throw new Error(folderExists(params.get('projectFolder')));
92
+ throw new CliError(folderExists(params.get('projectFolder')));
92
93
  }
93
94
 
94
95
  try {
@@ -33,6 +33,7 @@ select one by entering a number or leave empty to quit:
33
33
  ${formatObjectsFixedWidth(apps, ['name', 'url'], 3, { numbering: true })}
34
34
 
35
35
  Your choice: `);
36
+ console.log();
36
37
 
37
38
  if (!choice) {
38
39
  Logger.debug('No app URL selected');
@@ -0,0 +1,11 @@
1
+ const Logger = require('./logging');
2
+ class CliError extends Error {
3
+ stack = '';
4
+ constructor(message, error) {
5
+ super(Logger.getMessage(message, error));
6
+ }
7
+ }
8
+
9
+ module.exports = {
10
+ CliError
11
+ };
@@ -17,20 +17,13 @@ module.exports = class Logger {
17
17
  }
18
18
 
19
19
  static getMessage(errorText, error) {
20
- let message = errorText && errorText + '\n' || '';
20
+ let message = errorText ? errorText + '\n' : '';
21
21
  if (!error) {
22
22
  return message;
23
23
  }
24
- if (process.env.DEBUG) {
25
- if ('response' in error && error.response.data) {
26
- const body = error.response.data;
27
- if (body.error && body.error.message) {
28
- message += body.error.message + '\n';
29
- }
30
- }
24
+ message += error.response?.data?.error?.message || error.message;
25
+ if (process.env.DEBUG && error.stack) {
31
26
  message += '\n' + error.stack;
32
- } else {
33
- message += error.message;
34
27
  }
35
28
  return message;
36
29
  }
@@ -1,10 +1,12 @@
1
1
  const IP_ADDR = /^[1-9][0-9]{0,2}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/;
2
2
  const schemaRegex = /^https?:\/\//;
3
- const defaultSchema = 'https://';
3
+ const localhostRegex = /^(https?:\/\/)?(localhost|127\.0\.0\.1|\[?::1]?)\b/;
4
+ const httpSchema = 'http://';
5
+ const httpsSchema = 'https://';
4
6
 
5
7
  function extractSubdomain(url) {
6
8
  const hostname = new URL(url).hostname;
7
- if (hostname === 'localhost' || IP_ADDR.test(hostname)) {
9
+ if (localhostRegex.test(hostname) || IP_ADDR.test(hostname)) {
8
10
  return undefined;
9
11
  }
10
12
  return hostname.split('.')[0];
@@ -12,6 +14,8 @@ function extractSubdomain(url) {
12
14
 
13
15
  module.exports = {
14
16
  schemaRegex,
15
- defaultSchema,
17
+ localhostRegex,
18
+ httpSchema,
19
+ httpsSchema,
16
20
  extractSubdomain
17
21
  };
@@ -7,7 +7,7 @@ const { SettingsManager } = require('./settings_manager');
7
7
  module.exports = {
8
8
  extend: Extend.run,
9
9
  activate: Activate.run,
10
- login: AuthManager.loginAndUpdateSettings,
10
+ login: AuthManager.login,
11
11
  logout: AuthManager.logout,
12
12
  question: Question,
13
13
  config: SettingsManager.config
@@ -1,5 +1,6 @@
1
1
  const Logger = require('./helper/logging');
2
2
  const { escapeRegex } = require('./helper/string_helper');
3
+ const { CliError } = require('./helper/errors');
3
4
 
4
5
 
5
6
  // PARAM BASE TYPES
@@ -104,10 +105,10 @@ class Param {
104
105
  expectedType = typeof this.allowedValues[0];
105
106
  }
106
107
  if (typeof val !== expectedType) {
107
- throw new Error(`Failed to set '${(this.name)}' = ${JSON.stringify(val)}: expected value of type ${expectedType}`);
108
+ throw new CliError(`Failed to set '${(this.name)}' = ${JSON.stringify(val)}: expected value of type ${expectedType}`);
108
109
  }
109
110
  if (this.allowedValues && !(this.allowedValues).includes(val)) {
110
- throw new Error(`Failed to set '${(this.name)}' = ${JSON.stringify(val)}: expected one of ${JSON.stringify(this.allowedValues)}`);
111
+ throw new CliError(`Failed to set '${(this.name)}' = ${JSON.stringify(val)}: expected one of ${JSON.stringify(this.allowedValues)}`);
111
112
  }
112
113
  }
113
114
 
@@ -163,11 +164,13 @@ class BasicParams {
163
164
 
164
165
  // - auth-related
165
166
  this.passcode = new Param('passcode', '', { obfuscate: ObfuscationLevel.partial });
166
- this.tokenStorage = new Param('tokenStorage', undefined, { allowedValues: ['plain', 'keyring'] });
167
+ this.tokenStorage = new Param('tokenStorage', undefined, { allowedValues: ['plain', 'keyring'] });
167
168
  this.plain = new Param('plain', false, { internal: true }); // for backward compatibility - ignored
169
+ this.saveData = new Param('saveData', false, { internal: true });
168
170
  this.renewLogin = new Param('renewLogin', false);
169
- this.username = new Param('username', '');
171
+ this.username = new Param('username', '', { persist: Persistence.setting }); // only saved against localhost (non-productive); as setting due to ambiguous app URL
170
172
  this.password = new Param('password', '', { obfuscate: ObfuscationLevel.full });
173
+ this.isEmptyPassword = new Param('isEmptyPassword', false, { internal: true, persist: Persistence.setting }); // extra param so we can ensure we never save a password
171
174
  this.clientid = new Param('clientid', '');
172
175
  this.clientsecret = new Param('clientsecret', '', { obfuscate: ObfuscationLevel.partial });
173
176
 
@@ -244,7 +247,7 @@ class ParamCollection {
244
247
  */
245
248
  checkFrozen() {
246
249
  if (this.isFrozen) {
247
- throw new Error('cannot modify read-only parameter collection');
250
+ throw new CliError('cannot modify read-only parameter collection');
248
251
  }
249
252
  }
250
253
 
@@ -265,7 +268,7 @@ class ParamCollection {
265
268
  static mainName(name) {
266
269
  const paramDef = ParamDefs[name];
267
270
  if (!paramDef) {
268
- throw new Error(`Unknown parameter: '${name}'`);
271
+ throw new CliError(`Unknown parameter: '${name}'`);
269
272
  }
270
273
  return paramDef.mainName;
271
274
  }
@@ -0,0 +1,48 @@
1
+ const fs = require('fs');
2
+ const { join } = require('path');
3
+ const axios = require('axios');
4
+
5
+ const { unpackTarArchive } = require('@sap/cds/lib/utils/resources');
6
+
7
+ const { login } = require('./auth_manager');
8
+ const BaseCommand = require('./base_command');
9
+ const Logger = require('./helper/logging');
10
+ const { CliError } = require('./helper/errors');
11
+
12
+ module.exports = class Pull extends BaseCommand {
13
+
14
+ static async run(paramValues) {
15
+ try {
16
+ const params = await login(paramValues);
17
+ Logger.log(`Pulling base model from ${params.get('appUrl')}, for subdomain '${params.get('subdomain')}', to project folder '${params.get('projectFolder')}'`);
18
+
19
+ const projectFolder = params.get('projectFolder');
20
+ const targetDir = join(projectFolder, 'node_modules', this.getAppPackageName(projectFolder));
21
+ fs.rmSync(targetDir, { force: true, recursive: true });
22
+ fs.mkdirSync(targetDir, { recursive: true });
23
+
24
+ const tgzBuffer = Buffer.from(await this.getTgz(params));
25
+ return unpackTarArchive(tgzBuffer, targetDir, false);
26
+
27
+ } catch (error) {
28
+ throw new CliError(undefined, error);
29
+ }
30
+ }
31
+
32
+ static getAppPackageName(projectFolder) {
33
+ try {
34
+ return require(join(projectFolder, 'package.json')).cds.extends;
35
+ } catch (error) {
36
+ return '_base';
37
+ }
38
+ }
39
+
40
+ static async getTgz(params) {
41
+ const pullUrl = `${(params.get('appUrl'))}/-/cds/extensibility/pull`;
42
+ const options = { ...params.get('reqAuth'), responseType: 'arraybuffer' };
43
+
44
+ return axios.post(pullUrl, {}, options)
45
+ .then(response => response.data)
46
+ .catch(error => this.handleHttpError(error, params));
47
+ }
48
+ }
@@ -0,0 +1,45 @@
1
+ const fs = require('fs');
2
+ const { join } = require('path');
3
+ const axios = require('axios');
4
+
5
+ const { login } = require('./auth_manager');
6
+ const BaseCommand = require('./base_command');
7
+ const Logger = require('./helper/logging');
8
+ const { CliError } = require('./helper/errors');
9
+
10
+ module.exports = class Push extends BaseCommand {
11
+
12
+ static async run(paramValues) {
13
+ try {
14
+ const params = await login(paramValues);
15
+ const projectFolder = params.get('projectFolder');
16
+ Logger.log(`Pushing extension from project folder '${projectFolder}', for subdomain '${params.get('subdomain')}', to app URL ${params.get('appUrl')}`);
17
+
18
+ const tgz = fs.readFileSync(join(projectFolder, 'gen', 'extension.tgz'), { encoding: 'base64' });
19
+ const extensionName = this.getExtensionName(projectFolder);
20
+ return this.pushTgz(params, tgz, extensionName);
21
+
22
+ } catch (error) {
23
+ throw new CliError(undefined, error);
24
+ }
25
+ }
26
+
27
+ static getExtensionName(projectFolder) {
28
+ try {
29
+ return require(join(projectFolder, 'package.json')).name;
30
+ } catch (error) {
31
+ throw new CliError(`Extension project at ${projectFolder} is missing package.json`);
32
+ }
33
+ }
34
+
35
+ static async pushTgz(params, tgz, extensionName) {
36
+ const pushUrl = `${params.get('appUrl')}/-/cds/extensibility/push`;
37
+ const options = { ...params.get('reqAuth') };
38
+
39
+ return axios.post(pushUrl, {
40
+ extension: tgz,
41
+ tag: extensionName
42
+ }, options)
43
+ .catch(error => this.handleHttpError(error, params));
44
+ }
45
+ }
@@ -9,9 +9,11 @@ const { getAppFromSuggestions, getAppName, getSubdomain } = require('./helper/cf
9
9
  const isBas = require('./helper/env_helper');
10
10
  const Logger = require('./helper/logging');
11
11
  const Question = require('./helper/question');
12
- const { defaultSchema, extractSubdomain, schemaRegex } = require('./helper/url_helper');
12
+ const { CliError } = require('./helper/errors');
13
+ const { httpSchema, httpsSchema, extractSubdomain, schemaRegex, localhostRegex } = require('./helper/url_helper');
13
14
  const { ParamCollection, Persistence } = require('./params');
14
15
  const { capitalize } = require('./helper/string_helper');
16
+ const axios = require('axios');
15
17
 
16
18
  const CONFIG_SUBDIRS = {
17
19
  linux: '.config',
@@ -31,7 +33,8 @@ const KEYRING_DESC = {
31
33
  }
32
34
 
33
35
  const MTX_FULLY_QUALIFIED = 'com.sap.cds.mtx';
34
- const OAUTH_PATH = '/mtx/v1/oauth/token';
36
+ const OAUTH_PATH_OLD_MTX = '/mtx/v1/oauth/token';
37
+ const OAUTH_PATH_STREAMLINED_MTX = '/-/cds/login/token';
35
38
 
36
39
  const SETTINGS_DIR = path.join(os.homedir(), CONFIG_SUBDIRS[os.platform()] || '', MTX_FULLY_QUALIFIED);
37
40
  const SETTINGS_FILE = 'projects.json';
@@ -88,16 +91,38 @@ async function getSubdomainFromCfAndNotify(appName) {
88
91
  return subdomain;
89
92
  }
90
93
 
91
- function getTokenBaseUrl(params, renewUrl) {
94
+ async function getTokenBaseUrl(params, renewUrl) {
92
95
  if (params.has('tokenUrl') && !renewUrl) {
93
96
  return params.get('tokenUrl');
94
97
  }
95
98
  if (!params.has('appUrl')) {
96
- throw new Error('Failed to determine token URL: app URL not given');
99
+ throw new CliError('Failed to determine token URL: app URL not given');
97
100
  }
98
101
  const baseUrl = new URL(params.get('appUrl'));
99
- baseUrl.pathname = baseUrl.pathname.replace(/\/?$/, OAUTH_PATH);
100
- return baseUrl.toString();
102
+ const cleanPathname = pathname => baseUrl.pathname.replace(/\/?$/, pathname);
103
+ const getBaseUrl = pathname => {
104
+ const url = new URL(baseUrl.toString());
105
+ url.pathname = cleanPathname(pathname);
106
+ return url;
107
+ };
108
+
109
+ let existingPath;
110
+ const oauthPaths = [OAUTH_PATH_STREAMLINED_MTX/*, OAUTH_PATH_OLD_MTX*/]; // The old MTX does not support HEAD requests
111
+ for (const path of oauthPaths) {
112
+ try {
113
+ await axios.head(getBaseUrl(path).toString());
114
+ existingPath = path;
115
+ break;
116
+ } catch (e) {
117
+ if (e.response?.status === 404) {
118
+ continue;
119
+ } else {
120
+ throw CliError(`Failed with root cause ${e.message}`);
121
+ }
122
+ }
123
+ }
124
+ existingPath = existingPath ?? OAUTH_PATH_OLD_MTX;
125
+ return getBaseUrl(existingPath).toString();
101
126
  }
102
127
 
103
128
  function getKeyringAccountName(params) {
@@ -134,7 +159,16 @@ class SettingsManager {
134
159
  static async saveSettings(params) {
135
160
  Logger.debug(`Saving settings for project ${params.get('projectFolder')}`);
136
161
 
137
- await this._saveToFile(params.get('projectFolder'), params.toValueMap(Persistence.setting));
162
+ const paramsMap = params.toValueMap(Persistence.setting);
163
+ if (localhostRegex.test(params.get('appUrl'))) {
164
+ Logger.log(`Saving username${(params.get('isEmptyPassword') ? ' and empty-password hint' : '')} with project settings.`);
165
+ } else {
166
+ // Save only against localhost for security reasons.
167
+ Logger.debug('Not saving username because app is not recognized to run on localhost.');
168
+ delete paramsMap.username;
169
+ delete paramsMap.isEmptyPassword;
170
+ }
171
+ await this._saveToFile(params.get('projectFolder'), paramsMap);
138
172
 
139
173
  if (!params.has('token')) {
140
174
  return;
@@ -173,15 +207,22 @@ class SettingsManager {
173
207
 
174
208
  static async updateUrls(params, logout, loadedAppUrl) {
175
209
  async function addAppUrlAndSubdomain() {
176
- if (params.has('appUrl') && !schemaRegex.test(params.get('appUrl'))) {
177
- params.set('appUrl', defaultSchema + params.get('appUrl'));
178
- Logger.debug(`Added schema to app URL: ${params.get('appUrl')}`);
210
+ if (params.has('appUrl')) {
211
+ const appUrl = params.get('appUrl');
212
+ if (!schemaRegex.test(appUrl)) {
213
+ if (localhostRegex.test(appUrl)) {
214
+ params.set('appUrl', httpSchema + appUrl);
215
+ } else {
216
+ params.set('appUrl', httpsSchema + appUrl);
217
+ }
218
+ Logger.debug(`Added schema to app URL: ${params.get('appUrl')}`);
219
+ }
179
220
  }
180
221
  if (!logout) {
181
222
  if (!params.has('appUrl')) {
182
223
  Logger.debug('App URL not given');
183
224
  await getAppUrlAndSubdomainFromSuggestions(params);
184
- } else if (!params.has('subdomain')) {
225
+ } else if (!params.has('subdomain') && !localhostRegex.test(params.get('appUrl'))) {
185
226
  Logger.debug('Subdomain not given');
186
227
  const appName = await getAppName(params.get('appUrl'));
187
228
  if (appName) {
@@ -190,27 +231,28 @@ class SettingsManager {
190
231
  }
191
232
  }
192
233
  if (!params.has('appUrl')) {
193
- throw new Error('App URL not given. Please specify it or log in to Cloud Foundry and repeat this command.');
234
+ throw new CliError('App URL not given. Please specify it or log in to Cloud Foundry and repeat this command.');
194
235
  }
195
236
  if (!params.has('subdomain')) {
196
- params.set('subdomain', extractSubdomain(params.get('appUrl')));
197
- if (!params.has('subdomain')) {
198
- throw new Error('Subdomain not given');
237
+ const subdomain = extractSubdomain(params.get('appUrl'));
238
+ if (!subdomain) {
239
+ throw new CliError('Subdomain not given');
199
240
  }
200
- Logger.log(`Subdomain determined from app URL: ${params.get('subdomain')}`);
241
+ params.set('subdomain', subdomain);
242
+ Logger.log(`Subdomain determined from app URL: ${subdomain}`);
201
243
  }
202
244
  }
203
- function renewTokenUrl() {
245
+ async function renewTokenUrl() {
204
246
  const renewUrl = params.get('appUrl') !== loadedAppUrl;
205
247
  if (!params.get('tokenUrl') || renewUrl) {
206
- params.set('tokenUrl', getTokenBaseUrl(params, renewUrl));
248
+ params.set('tokenUrl', await getTokenBaseUrl(params, renewUrl));
207
249
  }
208
250
  }
209
251
 
210
252
  await addAppUrlAndSubdomain();
211
253
 
212
254
  if (!logout) {
213
- renewTokenUrl();
255
+ await renewTokenUrl();
214
256
  }
215
257
  }
216
258
 
@@ -226,7 +268,7 @@ class SettingsManager {
226
268
  return;
227
269
  }
228
270
  if (params.get('tokenStorage') === 'keyring') {
229
- throw new Error(`${capitalize(TOKEN_STORAGE_DESC.keyring)} requested but keytar not installed. Run 'npm install -g keytar' or ` +
271
+ throw new CliError(`${capitalize(TOKEN_STORAGE_DESC.keyring)} requested but keytar not installed. Run 'npm install -g keytar' or ` +
230
272
  `switch to ${TOKEN_STORAGE_DESC.plain} by adding '--plain' (discouraged).`);
231
273
  }
232
274
  if (params.get('renewLogin') || logout) {
@@ -236,23 +278,26 @@ class SettingsManager {
236
278
 
237
279
  static async addAuth(params, logout) {
238
280
  async function addPassword() {
239
- params.set('password', await Question.askQuestion('Password: ', undefined, true));
240
- if (params.get('password') === '') {
241
- throw new Error('Password cannot be empty');
281
+ if (params.get('isEmptyPassword')) {
282
+ params.set('password', '');
283
+ } else {
284
+ params.set('password', await Question.askQuestion('Password: ', undefined, true));
285
+ console.log();
242
286
  }
243
287
  }
244
288
  async function addClientSecret() {
245
289
  params.set('clientsecret', await Question.askQuestion('clientsecret: ', undefined, true));
290
+ console.log();
246
291
  if (params.get('clientsecret') === '') {
247
- throw new Error('clientsecret cannot be empty');
292
+ throw new CliError('clientsecret cannot be empty');
248
293
  }
249
294
  }
250
295
  async function addPasscode() {
251
296
  const prompt = `Passcode${params.get('passcodeUrl') ? ' (visit ' + params.get('passcodeUrl') + ' to generate)' : ''}: `;
252
297
  params.set('passcode', await Question.askQuestion(prompt, undefined, true));
253
- console.log(); // DON'T: process.stdout.write('\n');
298
+ console.log();
254
299
  if (params.get('passcode') === '') {
255
- throw new Error('Passcode cannot be empty');
300
+ throw new CliError('Passcode cannot be empty');
256
301
  }
257
302
  }
258
303
  function setRelevantAuth(auth) {
@@ -288,10 +333,19 @@ class SettingsManager {
288
333
  }
289
334
 
290
335
  if (params.has('username')) {
291
- if (!params.get('password') && !logout) {
336
+ if (!params.has('password') && !logout) {
292
337
  await addPassword();
293
338
  }
339
+ if (params.get('password') === '') {
340
+ params.set('isEmptyPassword', true);
341
+ // TODO check password only in production env
342
+ // throw new CliError('Password cannot be empty');
343
+ } else {
344
+ params.delete('isEmptyPassword');
345
+ }
294
346
  return;
347
+ } else {
348
+ params.delete('isEmptyPassword');
295
349
  }
296
350
 
297
351
  if (params.get('clientid') && !params.get('clientsecret') && !logout) {