@sap/cds-dk 6.1.5 → 6.2.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 (152) hide show
  1. package/CHANGELOG.md +30 -2
  2. package/bin/cds.js +7 -5
  3. package/bin/deploy/index.js +3 -2
  4. package/bin/deploy/to-sqlite.js +4 -3
  5. package/bin/help.js +4 -0
  6. package/bin/init.js +2 -1
  7. package/bin/login.js +23 -9
  8. package/bin/mock.js +1 -0
  9. package/bin/pull.js +24 -19
  10. package/bin/push.js +20 -9
  11. package/bin/subscribe.js +40 -10
  12. package/bin/watch.js +15 -10
  13. package/lib/bind/kubernetesCredentialsProvider.js +1 -0
  14. package/lib/client/activate.js +42 -51
  15. package/lib/client/auth_manager.js +7 -8
  16. package/lib/client/base_command.js +5 -2
  17. package/lib/client/extend.js +33 -38
  18. package/lib/client/helper/cf_helper.js +11 -11
  19. package/lib/client/helper/dependency_helper.js +2 -2
  20. package/lib/client/helper/errors.js +4 -3
  21. package/lib/client/helper/logging.js +17 -23
  22. package/lib/client/params.js +5 -5
  23. package/lib/client/pull.js +19 -21
  24. package/lib/client/push.js +16 -15
  25. package/lib/client/settings_manager.js +54 -55
  26. package/lib/compile/openapi/csdl2openapi.js +18 -12
  27. package/lib/import/message.js +1 -1
  28. package/lib/import/odata/metadataconverter/MetadataConverter.js +23 -17
  29. package/lib/import/odata/v2parser.js +43 -17
  30. package/lib/import/odata/v4parser.js +77 -22
  31. package/lib/init/constants.js +5 -2
  32. package/lib/init/index.js +16 -4
  33. package/lib/init/template/_merging/registry-mta.js +13 -0
  34. package/lib/init/template/approuter/index.js +9 -0
  35. package/lib/{k8s/connectivity/templates/connectivity.yaml → init/template/auditlog/files/auditlog.yaml} +0 -0
  36. package/lib/init/template/auditlog/index.js +46 -27
  37. package/lib/{k8s/connectivity/templates → init/template/connectivity/files}/connectivity-binding.yaml +0 -0
  38. package/lib/{k8s/connectivity/templates → init/template/connectivity/files}/connectivity-proxy-info.yaml +1 -1
  39. package/lib/init/template/connectivity/files/connectivity.yaml +1 -0
  40. package/lib/init/template/connectivity/files/values.yaml.hbs +26 -0
  41. package/lib/init/template/connectivity/index.js +41 -0
  42. package/lib/init/template/data/index.js +8 -8
  43. package/lib/init/template/destinations/files/destination.yaml +1 -0
  44. package/lib/init/template/destinations/index.js +44 -0
  45. package/lib/init/template/enterprise-messaging/files/cds.package.json +10 -0
  46. package/lib/init/template/enterprise-messaging/files/cds.profile.package.json.hbs +12 -0
  47. package/lib/init/template/enterprise-messaging/files/cdsrc.json +8 -0
  48. package/lib/init/template/enterprise-messaging/files/cdsrc.json.hbs +10 -0
  49. package/lib/init/template/enterprise-messaging/files/event-mesh.yaml +1 -0
  50. package/lib/init/template/enterprise-messaging/files/mta.yaml.hbs +13 -0
  51. package/lib/init/template/enterprise-messaging/files/values.yaml +8 -0
  52. package/lib/init/template/enterprise-messaging/index.js +56 -0
  53. package/lib/init/template/extensibility/files/cdsrc.json +5 -0
  54. package/lib/init/template/extensibility/files/cdsrc.json.hbs +7 -0
  55. package/lib/init/template/extensibility/index.js +18 -13
  56. package/lib/{k8s/hana-deployer/templates → init/template/hana/files}/hana-deployer-job.yaml +0 -0
  57. package/lib/init/template/hana/files/java/{cdsrc.json.hbs → cds.json.hbs} +0 -0
  58. package/lib/init/template/hana/files/java/{profile.cdsrc.json.hbs → cds.profile.json.hbs} +0 -0
  59. package/lib/init/template/hana/files/java/cds.shortcut.json.hbs +5 -0
  60. package/lib/init/template/hana/files/java/cds.shortcut.profile.json.hbs +8 -0
  61. package/lib/init/template/hana/files/java/db/package.json +2 -1
  62. package/lib/init/template/hana/files/mta.yaml.hbs +6 -0
  63. package/lib/init/template/hana/files/nodejs/{cds.package.json.hbs → cds.json.hbs} +0 -0
  64. package/lib/init/template/hana/files/nodejs/{cds.profile.package.json.hbs → cds.profile.json.hbs} +0 -0
  65. package/lib/init/template/hana/files/nodejs/cds.shortcut.json.hbs +7 -0
  66. package/lib/init/template/hana/files/nodejs/cds.shortcut.profile.json.hbs +10 -0
  67. package/lib/init/template/hana/files/values.yaml.hbs +19 -0
  68. package/lib/init/template/hana/index.js +29 -29
  69. package/lib/{k8s → init/template/helm}/Model.d.ts +0 -0
  70. package/lib/{k8s/resources → init/template}/helm/chart/templates/NOTES.txt +0 -0
  71. package/lib/{k8s/resources → init/template}/helm/chart/templates/_deployment_helpers.tpl +0 -0
  72. package/lib/{k8s/resources → init/template}/helm/chart/templates/_helpers.tpl +4 -5
  73. package/lib/{k8s/resources → init/template}/helm/chart/templates/_sapcp_helpers.tpl +0 -0
  74. package/lib/{k8s/resources/helm/chart/schemas → init/template/helm/chart}/values.root.schema.json +0 -0
  75. package/lib/{k8s/resources/helm/chart/schemas → init/template/helm/chart}/values.schema.json +0 -0
  76. package/lib/init/template/helm/files/Chart.yaml.hbs +13 -0
  77. package/lib/init/template/helm/files/values.yaml.hbs +26 -0
  78. package/lib/init/template/helm/index.js +74 -14
  79. package/lib/{k8s/deployment → init/template/helm}/subchart/Chart.yaml +0 -0
  80. package/lib/{k8s/deployment → init/template/helm}/subchart/templates/_helpers.tpl +0 -0
  81. package/lib/{k8s/deployment → init/template/helm}/subchart/templates/api-rule.yaml +0 -0
  82. package/lib/{k8s/deployment → init/template/helm}/subchart/templates/deployment.yaml +0 -0
  83. package/lib/{k8s/deployment → init/template/helm}/subchart/templates/network-policy.yaml +0 -0
  84. package/lib/{k8s/deployment → init/template/helm}/subchart/templates/pod-disruption-budget.yaml +0 -0
  85. package/lib/{k8s/deployment → init/template/helm}/subchart/templates/secret.yaml +0 -0
  86. package/lib/{k8s/deployment → init/template/helm}/subchart/templates/service-binding.yaml +0 -0
  87. package/lib/{k8s/deployment → init/template/helm}/subchart/templates/service.yaml +0 -0
  88. package/lib/{k8s/deployment → init/template/helm}/subchart/values-lint.yaml +0 -0
  89. package/lib/{k8s/deployment → init/template/helm}/subchart/values.md +0 -0
  90. package/lib/{k8s/deployment → init/template/helm}/subchart/values.schema.json +0 -0
  91. package/lib/{k8s/deployment → init/template/helm}/subchart/values.yaml +0 -0
  92. package/lib/{k8s/html5-apps-deployer/templates → init/template/html5-repo/files}/_html5_deployment_helpers.tpl +0 -0
  93. package/lib/{k8s/html5-apps-deployer/templates → init/template/html5-repo/files}/html5-apps-deployer-job.yaml +1 -1
  94. package/lib/{k8s/html5-apps-repo-host/templates → init/template/html5-repo/files}/html5-apps-repo-host.yaml +0 -0
  95. package/lib/init/template/html5-repo/index.js +59 -0
  96. package/lib/init/template/kibana-logging/index.js +8 -0
  97. package/lib/init/template/lint/index.js +1 -4
  98. package/lib/init/template/mta/files/mta.yaml.hbs +3 -2
  99. package/lib/init/template/mta/index.js +6 -1
  100. package/lib/init/template/multitenancy/files/java/.cdsrc.json +3 -0
  101. package/lib/init/template/multitenancy/files/java/package.json +2 -6
  102. package/lib/{k8s/multitenancy/templates → init/template/multitenancy/files}/saas-registry.yaml +3 -3
  103. package/lib/init/template/multitenancy/files/xsuaa.yaml +1 -0
  104. package/lib/init/template/multitenancy/index.js +53 -26
  105. package/lib/init/template/nodejs/files/.gitignore.hbs +1 -0
  106. package/lib/init/template/templateBase.js +1 -1
  107. package/lib/init/template/xsuaa/files/values.yaml.hbs +10 -0
  108. package/lib/init/template/xsuaa/files/xsuaa.yaml +1 -0
  109. package/lib/init/template/xsuaa/index.js +34 -22
  110. package/lib/init/util/fsUtil.js +11 -5
  111. package/lib/init/util/projectReader.js +24 -32
  112. package/lib/init/util/templateUtil.js +17 -5
  113. package/lib/lint/Linter.js +9 -17
  114. package/lib/lint/checks.js +0 -1
  115. package/lib/lint/generators.js +2 -192
  116. package/lib/lint/io.js +1 -3
  117. package/lib/watch/node-watch.js +2 -2
  118. package/npm-shrinkwrap.json +1400 -779
  119. package/package.json +5 -4
  120. package/lib/init/template/helm/update/index.js +0 -28
  121. package/lib/k8s/ChartAdder.js +0 -75
  122. package/lib/k8s/ChartBuilder.js +0 -192
  123. package/lib/k8s/Context.js +0 -71
  124. package/lib/k8s/ImageUtil.js +0 -21
  125. package/lib/k8s/auditlog/index.js +0 -29
  126. package/lib/k8s/auditlog/templates/auditlog.yaml +0 -1
  127. package/lib/k8s/buildModel.js +0 -68
  128. package/lib/k8s/connectivity/index.js +0 -80
  129. package/lib/k8s/deployment/index.js +0 -79
  130. package/lib/k8s/deployment/subchart-override/templates/_web_application_helpers.tpl +0 -81
  131. package/lib/k8s/destinations/index.js +0 -40
  132. package/lib/k8s/destinations/lib.js +0 -16
  133. package/lib/k8s/destinations/templates/destinations.yaml +0 -1
  134. package/lib/k8s/docker/push.js +0 -8
  135. package/lib/k8s/event-mesh/index.js +0 -53
  136. package/lib/k8s/event-mesh/lib.js +0 -9
  137. package/lib/k8s/event-mesh/templates/event-mesh.yaml +0 -1
  138. package/lib/k8s/hana-deployer/index.js +0 -48
  139. package/lib/k8s/helm/Release.js +0 -80
  140. package/lib/k8s/html5-apps-deployer/index.js +0 -53
  141. package/lib/k8s/html5-apps-repo-host/index.js +0 -26
  142. package/lib/k8s/html5-apps-repo-host/lib.js +0 -16
  143. package/lib/k8s/index.js +0 -153
  144. package/lib/k8s/modules.js +0 -36
  145. package/lib/k8s/multitenancy/index.js +0 -24
  146. package/lib/k8s/multitenancy/lib.js +0 -25
  147. package/lib/k8s/readProject.js +0 -14
  148. package/lib/k8s/util.js +0 -186
  149. package/lib/k8s/wrapFeature.js +0 -55
  150. package/lib/k8s/xsuaa/index.js +0 -64
  151. package/lib/k8s/xsuaa/lib.js +0 -17
  152. package/lib/k8s/xsuaa/templates/xsuaa.yaml +0 -1
@@ -1,34 +1,35 @@
1
+ const cds = require ('../cds'), { local } = cds.utils
1
2
  const fs = require('fs');
2
3
  const { join } = require('path');
3
4
  const axios = require('axios');
4
5
 
5
6
  const { login } = require('./auth_manager');
6
7
  const BaseCommand = require('./base_command');
7
- const Logger = require('./helper/logging');
8
- const { CliError } = require('./helper/errors');
8
+ const { CliError } = require('./helper/errors'); // REVISIT: add and use a this.error() method instead
9
9
 
10
10
  module.exports = class Push extends BaseCommand {
11
11
 
12
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
- }
13
+ const params = await login(paramValues);
14
+ const projectFolder = params.get('projectFolder');
15
+ const tar = join(projectFolder, 'gen', 'extension.tgz')
16
+ const url = params.get('appUrl')
17
+ const subdomain = params.get('subdomain') // REVISIT: Why are subdomains not transparently encoded in URLs?
18
+ const target = { url }; if (subdomain) target.subdomain = subdomain
19
+ const source = { src: local(tar) }
20
+ console.log(`\nPushing extension from`, source, 'to', target);
21
+
22
+ const tgz = fs.readFileSync(tar, { encoding: 'base64' });
23
+ const extensionName = this.getExtensionName(projectFolder);
24
+ await this.pushTgz(params, tgz, extensionName);
25
+ console.log ('Activation succeeded.\n')
25
26
  }
26
27
 
27
28
  static getExtensionName(projectFolder) {
28
29
  try {
29
30
  return require(join(projectFolder, 'package.json')).name;
30
31
  } catch (error) {
31
- throw new CliError(`Extension project at ${projectFolder} is missing package.json`);
32
+ throw new CliError(`Extension project at ${projectFolder} is missing package.json file`, error);
32
33
  }
33
34
  }
34
35
 
@@ -1,36 +1,37 @@
1
1
  const fs = require('fs');
2
2
  const os = require('os');
3
3
  const path = require('path');
4
+ const axios = require('axios');
4
5
 
5
6
  const requireGlobal = require('./helper/dependency_helper');
6
7
  const keytar = requireGlobal('keytar');
7
8
 
9
+ const log = require('./helper/logging');
8
10
  const { getAppFromSuggestions, getAppName, getSubdomain } = require('./helper/cf_helper');
9
11
  const isBas = require('./helper/env_helper');
10
- const Logger = require('./helper/logging');
11
12
  const Question = require('./helper/question');
12
13
  const { CliError } = require('./helper/errors');
13
14
  const { httpSchema, httpsSchema, extractSubdomain, schemaRegex, localhostRegex } = require('./helper/url_helper');
14
- const { ParamCollection, Persistence } = require('./params');
15
15
  const { capitalize } = require('./helper/string_helper');
16
- const axios = require('axios');
16
+
17
+ const { ParamCollection, Persistence } = require('./params');
17
18
 
18
19
  const CONFIG_SUBDIRS = {
19
20
  linux: '.config',
20
21
  darwin: path.join('Library', 'Preferences'),
21
22
  win32: 'AppData'
22
- }
23
+ };
23
24
 
24
25
  const TOKEN_STORAGE_DESC = {
25
26
  plain: 'plain-text storage',
26
27
  keyring: 'keyring'
27
- }
28
+ };
28
29
 
29
30
  const KEYRING_DESC = {
30
31
  linux: 'libsecret',
31
32
  darwin: 'Keychain',
32
33
  win32: 'Credential Vault'
33
- }
34
+ };
34
35
 
35
36
  const MTX_FULLY_QUALIFIED = 'com.sap.cds.mtx';
36
37
  const OAUTH_PATH_OLD_MTX = '/mtx/v1/oauth/token';
@@ -84,9 +85,9 @@ async function getAppUrlAndSubdomainFromSuggestions(params) {
84
85
  async function getSubdomainFromCfAndNotify(appName) {
85
86
  const subdomain = await getSubdomain(appName);
86
87
  if (subdomain) {
87
- Logger.log(`Subdomain determined from CF app environment: ${subdomain}`);
88
+ console.log('Subdomain determined from CF app environment:', subdomain);
88
89
  } else {
89
- Logger.log('Failed to determine subdomain from CF app environment');
90
+ console.warn('Failed to determine subdomain from CF app environment');
90
91
  }
91
92
  return subdomain;
92
93
  }
@@ -113,12 +114,11 @@ async function getTokenBaseUrl(params, renewUrl) {
113
114
  await axios.head(getBaseUrl(path).toString());
114
115
  existingPath = path;
115
116
  break;
116
- } catch (e) {
117
- if (e.response?.status === 404) {
117
+ } catch (error) {
118
+ if (error.response?.status === 404) {
118
119
  continue;
119
- } else {
120
- throw new CliError(`Failed with root cause ${e.message}`);
121
120
  }
121
+ throw error;
122
122
  }
123
123
  }
124
124
  existingPath = existingPath ?? OAUTH_PATH_OLD_MTX;
@@ -129,16 +129,15 @@ function getKeyringAccountName(params) {
129
129
  return `${params.get('appUrl')}|${params.get('subdomain')}`;
130
130
  }
131
131
 
132
-
133
132
  function logConfigPaths() {
134
- Logger.debug(`Settings are stored in ${CONFIG.paths.settings}`);
135
- Logger.debug(`Authentication data is stored in ${CONFIG.paths.auth} (${TOKEN_STORAGE_DESC.plain}) or ${CONFIG.keyringDesignation} (${TOKEN_STORAGE_DESC.keyring})`);
133
+ log.debug(`Settings are stored in ${CONFIG.paths.settings}`);
134
+ log.debug(`Authentication data is stored in ${CONFIG.paths.auth} (${TOKEN_STORAGE_DESC.plain}) or ${CONFIG.keyringDesignation} (${TOKEN_STORAGE_DESC.keyring})`);
136
135
  }
137
136
 
138
137
  function notifyLoggedIn(params) {
139
138
  return params.get('renewLogin')
140
- ? Logger.log.bind(Logger)
141
- : Logger.debug.bind(Logger);
139
+ ? console.log.bind(console)
140
+ : log.debug;
142
141
  }
143
142
 
144
143
  function emptyData(all) {
@@ -157,14 +156,14 @@ class SettingsManager {
157
156
 
158
157
  // Call with 'projectFolder' param as a resolved path.
159
158
  static async saveSettings(params) {
160
- Logger.debug(`Saving settings for project ${params.get('projectFolder')}`);
159
+ log.debug(`Saving settings for project ${params.get('projectFolder')}`);
161
160
 
162
161
  const paramsMap = params.toValueMap(Persistence.setting);
163
162
  if (localhostRegex.test(params.get('appUrl'))) {
164
- Logger.log(`Saving username${(params.get('isEmptyPassword') ? ' and empty-password hint' : '')} with project settings.`);
163
+ console.log(`Saving username${(params.get('isEmptyPassword') ? ' and empty-password hint' : '')} with project settings.`);
165
164
  } else {
166
165
  // Save only against localhost for security reasons.
167
- Logger.debug('Not saving username because app is not recognized to run on localhost.');
166
+ log.debug('Not saving username because app is not recognized to run on localhost.');
168
167
  delete paramsMap.username;
169
168
  delete paramsMap.isEmptyPassword;
170
169
  }
@@ -185,7 +184,7 @@ class SettingsManager {
185
184
  } else if (params.get('tokenStorage') === 'keyring') {
186
185
  await this._saveAuthToKeyring(params, params.toValueMap(Persistence.auth));
187
186
  } else {
188
- Logger.log('Note: the authentication token is not saved by default. To save the token for later commands, please run `cds login`.');
187
+ console.log('Note: the authentication token is not saved by default. To save the token for later commands, please run `cds login`.');
189
188
  }
190
189
  }
191
190
 
@@ -194,7 +193,7 @@ class SettingsManager {
194
193
  params.set('projectFolder', projectFolder);
195
194
 
196
195
  const loadedSettings = await this._loadFromFile(projectFolder);
197
- Logger.debug(`Loaded project settings for ${params.get('projectFolder')}: ${loadedSettings.format()}`);
196
+ log.debug(`Loaded project settings for ${params.get('projectFolder')}: ${loadedSettings.format()}`);
198
197
  const loadedAppUrl = loadedSettings.get('appUrl');
199
198
 
200
199
  params.mergeLower(loadedSettings);
@@ -202,7 +201,7 @@ class SettingsManager {
202
201
  await this.updateUrls(params, logout, loadedAppUrl);
203
202
  await this.checkKeytar(params, logout);
204
203
  await this.addAuth(params, logout);
205
- Logger.debug(`Effective project settings: ${params.format()}`);
204
+ log.debug(`Effective project settings: ${params.format()}`);
206
205
  }
207
206
 
208
207
  static async updateUrls(params, logout, loadedAppUrl) {
@@ -215,15 +214,15 @@ class SettingsManager {
215
214
  } else {
216
215
  params.set('appUrl', httpsSchema + appUrl);
217
216
  }
218
- Logger.debug(`Added schema to app URL: ${params.get('appUrl')}`);
217
+ log.debug(`Added schema to app URL: ${params.get('appUrl')}`);
219
218
  }
220
219
  }
221
220
  if (!logout) {
222
221
  if (!params.has('appUrl')) {
223
- Logger.debug('App URL not given');
222
+ log.debug('App URL not given');
224
223
  await getAppUrlAndSubdomainFromSuggestions(params);
225
224
  } else if (!params.has('subdomain') && !localhostRegex.test(params.get('appUrl'))) {
226
- Logger.debug('Subdomain not given');
225
+ log.debug('Subdomain not given');
227
226
  const appName = await getAppName(params.get('appUrl'));
228
227
  if (appName) {
229
228
  params.set('subdomain', await getSubdomainFromCfAndNotify(appName));
@@ -233,13 +232,13 @@ class SettingsManager {
233
232
  if (!params.has('appUrl')) {
234
233
  throw new CliError('App URL not given. Please specify it or log in to Cloud Foundry and repeat this command.');
235
234
  }
236
- if (!params.has('subdomain')) {
235
+ if (!params.has('subdomain') && !params.has('username')) {
237
236
  const subdomain = extractSubdomain(params.get('appUrl'));
238
237
  if (!subdomain) {
239
238
  throw new CliError('Subdomain not given');
240
239
  }
241
240
  params.set('subdomain', subdomain);
242
- Logger.log(`Subdomain determined from app URL: ${subdomain}`);
241
+ console.log('Subdomain determined from app URL:', subdomain);
243
242
  }
244
243
  }
245
244
  async function renewTokenUrl() {
@@ -259,7 +258,7 @@ class SettingsManager {
259
258
  static async checkKeytar(params, logout) {
260
259
  if (isBas()) {
261
260
  if (params.get('tokenStorage') === 'keyring') {
262
- Logger.log(`NOTE: ${capitalize(TOKEN_STORAGE_DESC.keyring)} not supported on SAP Business Application Studio. Switching to ${TOKEN_STORAGE_DESC.plain}.`);
261
+ console.log('NOTE:', capitalize(TOKEN_STORAGE_DESC.keyring), 'not supported on SAP Business Application Studio. Switching to', TOKEN_STORAGE_DESC.plain, '.');
263
262
  params.set('tokenStorage', 'plain');
264
263
  }
265
264
  return;
@@ -268,11 +267,11 @@ class SettingsManager {
268
267
  return;
269
268
  }
270
269
  if (params.get('tokenStorage') === 'keyring') {
271
- throw new CliError(`${capitalize(TOKEN_STORAGE_DESC.keyring)} requested but keytar not installed. Run 'npm install -g keytar' or ` +
272
- `switch to ${TOKEN_STORAGE_DESC.plain} by adding '--plain' (discouraged).`);
270
+ throw new CliError(capitalize(TOKEN_STORAGE_DESC.keyring) + ' requested but keytar not installed. ' +
271
+ 'Run `npm install -g keytar` or switch to ' + TOKEN_STORAGE_DESC.plain + ' by adding `--plain` (discouraged).');
273
272
  }
274
273
  if (params.get('renewLogin') || logout) {
275
- Logger.log(`Disabling ${TOKEN_STORAGE_DESC.keyring} functionality: 'keytar' module not found. Run 'npm install -g keytar' to install it.`);
274
+ console.log('Disabling', TOKEN_STORAGE_DESC.keyring, 'functionality: `keytar` module not found. Run `npm install -g keytar` to install it.');
276
275
  }
277
276
  }
278
277
 
@@ -289,7 +288,7 @@ class SettingsManager {
289
288
  params.set('clientsecret', await Question.askQuestion('clientsecret: ', undefined, true));
290
289
  console.log();
291
290
  if (params.get('clientsecret') === '') {
292
- throw new CliError('clientsecret cannot be empty');
291
+ throw new CliError('Clientsecret cannot be empty');
293
292
  }
294
293
  }
295
294
  async function addPasscode() {
@@ -309,7 +308,7 @@ class SettingsManager {
309
308
  if (auth.keyring.has('tokenStorage') && auth.plain.has('tokenStorage')) {
310
309
  // Both storage places contain data: retrieve from selected or keyring.
311
310
  storage = storage || 'keyring';
312
- Logger.debug('WARNING: authentication data found in both kinds of storage. ' +
311
+ log.debug('WARNING: authentication data found in both kinds of storage. ' +
313
312
  `Using data from ${TOKEN_STORAGE_DESC[storage]}; other storage will be cleared when next saving.`);
314
313
 
315
314
  params.merge(auth[storage]);
@@ -317,7 +316,7 @@ class SettingsManager {
317
316
 
318
317
  } else if (storage && auth[other(storage)].has('tokenStorage')) {
319
318
  // Selected storage contains no data, but other one does: retrieve from other storage, but earmark migration to selected one.
320
- Logger.debug(`Using authentication data from ${TOKEN_STORAGE_DESC[other(storage)]}; will be migrated to other storage on save.`);
319
+ log.debug(`Using authentication data from ${TOKEN_STORAGE_DESC[other(storage)]}; will be migrated to other storage on save.`);
321
320
 
322
321
  params.merge(auth[other(storage)]);
323
322
  params.set('tokenStorage', storage);
@@ -326,7 +325,7 @@ class SettingsManager {
326
325
  } else {
327
326
  // One storage contains data, and there's no conflict with selection.
328
327
  const storage = auth.keyring.get('tokenStorage') || auth.plain.get('tokenStorage');
329
- Logger.debug(`Using authentication data from ${TOKEN_STORAGE_DESC[storage]}.`);
328
+ log.debug(`Using authentication data from ${TOKEN_STORAGE_DESC[storage]}.`);
330
329
 
331
330
  params.merge(auth[storage]);
332
331
  }
@@ -359,7 +358,7 @@ class SettingsManager {
359
358
 
360
359
  setRelevantAuth(auth);
361
360
 
362
- if (!params.has('token') && !params.has('passcode') && !logout) {
361
+ if (!params.has('token') && !params.has('passcode') && !params.has('clientsecret') && !logout) {
363
362
  await addPasscode();
364
363
  }
365
364
  }
@@ -372,11 +371,11 @@ class SettingsManager {
372
371
  let fromStorage = fromOtherStorage && other(params.get('tokenStorage'))
373
372
  const deleteBoth = !fromStorage && allParams.get('tokenStorage') && allParams.get('clearOtherTokenStorage');
374
373
 
375
- Logger.debug(`Deleting authentication data${fromOtherStorage ? ' from other storage' : ''} for ${target}`);
374
+ log.debug(`Deleting authentication data${fromOtherStorage ? ' from other storage' : ''} for ${target}`);
376
375
 
377
376
  fromStorage = fromStorage || allParams.get('tokenStorage');
378
377
  if (!fromStorage) {
379
- Logger.log(`Failed to delete authentication data: none found for ${target}`);
378
+ console.log('Failed to delete authentication data: none found for', target);
380
379
  return;
381
380
  }
382
381
 
@@ -417,9 +416,9 @@ class SettingsManager {
417
416
  await this.deleteToken(new ParamCollection({ appUrl, subdomain }));
418
417
  }
419
418
  }
420
- Logger.log(`Deleted settings for nonexistent project folders:${deletionFolders.map(folder => ' ' + folder)}`);
419
+ console.log('Deleted settings for nonexistent project folders:', deletionFolders.map(folder => ' ' + folder));
421
420
  } else {
422
- Logger.log('All settings seem valid');
421
+ console.log('All settings seem valid');
423
422
  }
424
423
  }
425
424
 
@@ -428,19 +427,19 @@ class SettingsManager {
428
427
  if (paramValues !== null) {
429
428
  paramValuesByFolder[projectFolder] = paramValues;
430
429
  await this._saveAllSettingsToFile(paramValuesByFolder);
431
- Logger.debug(`Saved project settings: ${JSON.stringify(paramValues)}`);
430
+ log.debug(`Saved project settings: ${JSON.stringify(paramValues)}`);
432
431
  } else {
433
432
  delete paramValuesByFolder[projectFolder];
434
433
  await this._saveAllSettingsToFile(paramValuesByFolder);
435
- Logger.log('Deleted project settings');
434
+ console.log('Deleted project settings');
436
435
  }
437
436
  }
438
437
 
439
438
  static async _saveAllSettingsToFile(paramValuesByFolder) {
440
- Logger.debug(`Saving all settings to ${CONFIG.paths.settings}`);
439
+ log.debug(`Saving all settings to ${CONFIG.paths.settings}`);
441
440
  await fs.promises.mkdir(path.dirname(CONFIG.paths.settings), { recursive: true })
442
441
  await fs.promises.writeFile(CONFIG.paths.settings, JSON.stringify(paramValuesByFolder, null, 2));
443
- Logger.debug('Saved settings');
442
+ log.debug('Saved settings');
444
443
  }
445
444
 
446
445
  static async _saveAuthToFile(params, authValues) {
@@ -460,7 +459,7 @@ class SettingsManager {
460
459
  }
461
460
  await fs.promises.mkdir(path.dirname(CONFIG.paths.auth), { recursive: true })
462
461
  await fs.promises.writeFile(CONFIG.paths.auth, JSON.stringify(allAuthValues, null, 2));
463
- Logger.log(`Deleted authentication data from ${location}`);
462
+ console.log('Deleted authentication data from', location);
464
463
  } else {
465
464
  notifyLoggedIn(params)(`No authentication data to delete from ${location}`);
466
465
  }
@@ -478,7 +477,7 @@ class SettingsManager {
478
477
  notifyLoggedIn(params)(`Saved authentication data to ${location}`);
479
478
  } else {
480
479
  await keytar.deletePassword(MTX_FULLY_QUALIFIED, getKeyringAccountName(params));
481
- Logger.log(`Deleted authentication data from ${location}`);
480
+ console.log('Deleted authentication data from', location);
482
481
  }
483
482
  }
484
483
 
@@ -486,7 +485,7 @@ class SettingsManager {
486
485
  const all = !projectFolder;
487
486
 
488
487
  if (!fs.existsSync(CONFIG.paths.settings)) {
489
- Logger.debug('Settings file not found');
488
+ log.debug('Settings file not found');
490
489
  return emptyData(all);
491
490
  }
492
491
 
@@ -494,7 +493,7 @@ class SettingsManager {
494
493
  try {
495
494
  settingsByFolder = JSON.parse((await fs.promises.readFile(CONFIG.paths.settings)).toString());
496
495
  } catch (err) {
497
- Logger.debug('Empty settings file');
496
+ log.debug('Empty settings file');
498
497
  return emptyData(all);
499
498
  }
500
499
 
@@ -503,7 +502,7 @@ class SettingsManager {
503
502
  }
504
503
 
505
504
  if (!settingsByFolder[projectFolder]) {
506
- Logger.debug(`No settings found for project ${projectFolder}`);
505
+ log.debug(`No settings found for project ${projectFolder}`);
507
506
  return new ParamCollection();
508
507
  }
509
508
 
@@ -514,7 +513,7 @@ class SettingsManager {
514
513
  const all = !appUrl;
515
514
 
516
515
  if (!fs.existsSync(CONFIG.paths.auth)) {
517
- Logger.debug('Authentication-data file not found');
516
+ log.debug('Authentication-data file not found');
518
517
  return emptyData(all);
519
518
  }
520
519
 
@@ -522,7 +521,7 @@ class SettingsManager {
522
521
  try {
523
522
  allAuth = JSON.parse((await fs.promises.readFile(CONFIG.paths.auth)).toString());
524
523
  } catch (err) {
525
- Logger.debug('Empty authentication-data file');
524
+ log.debug('Empty authentication-data file');
526
525
  return emptyData(all);
527
526
  }
528
527
 
@@ -531,14 +530,14 @@ class SettingsManager {
531
530
  }
532
531
 
533
532
  if (!allAuth[appUrl]) {
534
- Logger.debug(`File contains no authentication data for app URL ${appUrl}`);
533
+ log.debug(`File contains no authentication data for app URL ${appUrl}`);
535
534
  return new ParamCollection();
536
535
  }
537
536
 
538
537
  const authForSubdomain = allAuth[appUrl][subdomain];
539
538
 
540
539
  if (!authForSubdomain) {
541
- Logger.debug(`File contains no authentication data for subdomain ${subdomain} (app URL: ${appUrl})`);
540
+ log.debug(`File contains no authentication data for subdomain ${subdomain} (app URL: ${appUrl})`);
542
541
  return new ParamCollection();
543
542
  }
544
543
 
@@ -557,7 +556,7 @@ class SettingsManager {
557
556
  const authString = await keytar.getPassword(MTX_FULLY_QUALIFIED, getKeyringAccountName(new ParamCollection({ appUrl, subdomain })));
558
557
 
559
558
  if (!authString) {
560
- Logger.debug(`${capitalize(TOKEN_STORAGE_DESC.keyring)} contains no authentication data for URL ${appUrl} and subdomain '${subdomain}'`);
559
+ log.debug(`${capitalize(TOKEN_STORAGE_DESC.keyring)} contains no authentication data for URL ${appUrl} and subdomain '${subdomain}'`);
561
560
  return new ParamCollection();
562
561
  }
563
562
 
@@ -221,7 +221,8 @@ module.exports.csdl2openapi = function (
221
221
  Object.keys(terms).forEach(vocab => {
222
222
  voc[vocab] = {};
223
223
  terms[vocab].forEach(term => {
224
- voc[vocab][term] = '@' + alias['Org.OData.' + vocab + '.V1'] + '.' + term;
224
+ if (alias['Org.OData.' + vocab + '.V1'] != undefined)
225
+ voc[vocab][term] = '@' + alias['Org.OData.' + vocab + '.V1'] + '.' + term;
225
226
  });
226
227
  });
227
228
 
@@ -1785,7 +1786,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
1785
1786
  * @param {object} type Structured type
1786
1787
  * @return {object} Map of Schemas Objects
1787
1788
  */
1788
- function schemasForStructuredType(schemas, qualifier, name, type, suffix) {
1789
+ function schemasForStructuredType(schemas, qualifier, name, type, suffix) {
1789
1790
  const schemaName = qualifier + "." + name + suffix;
1790
1791
  const baseName = qualifier + "." + name;
1791
1792
  const isKey = keyMap(type);
@@ -1796,18 +1797,19 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
1796
1797
  Object.keys(properties).forEach(iName => {
1797
1798
  const property = properties[iName];
1798
1799
  if (suffix === SUFFIX.read) schemaProperties[iName] = getSchema(property);
1799
- if((Object.prototype.hasOwnProperty.call(property, '@Common.FieldControl')) && property['@Common.FieldControl'] === 'Mandatory')
1800
- { required.push(iName) }
1800
+ if ((Object.prototype.hasOwnProperty.call(property, '@Common.FieldControl')) && property['@Common.FieldControl'] === 'Mandatory') { required.push(iName) }
1801
1801
  if (property.$Kind == 'NavigationProperty') {
1802
1802
  if (property.$Collection && suffix === "") {
1803
1803
  schemaProperties[`${iName}@${csdl.$Version === '4.0' ? 'odata.' : ''}count`] = ref('count');
1804
1804
  }
1805
- if (suffix === SUFFIX.create)
1806
- schemaProperties[iName] = getSchema(property, SUFFIX.create);
1807
- if (suffix === SUFFIX.update && deepUpdate)
1808
- schemaProperties[iName] = getSchema(property, SUFFIX.create);
1809
- } else {
1810
1805
  if (property[voc.Core.Permissions] != "Read" && !property[voc.Core.Computed] && property.$ContainsTarget) {
1806
+ if (suffix === SUFFIX.create)
1807
+ schemaProperties[iName] = getSchema(property, SUFFIX.create);
1808
+ if (suffix === SUFFIX.update && deepUpdate)
1809
+ schemaProperties[iName] = getSchema(property, SUFFIX.create);
1810
+ }
1811
+ } else {
1812
+ if (property[voc.Core.Permissions] === "Read" || property[voc.Core.Computed]) {
1811
1813
  let index = required.indexOf(iName);
1812
1814
  if (index != -1) required.splice(index, 1);
1813
1815
  } else {
@@ -1838,7 +1840,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
1838
1840
  if (derivedTypes[baseName]) {
1839
1841
  schemas[schemaName].anyOf = [];
1840
1842
  derivedTypes[baseName].forEach((derivedType) => {
1841
- schemas[schemaName].anyOf.push(ref(derivedType, suffix));
1843
+ schemas[schemaName].anyOf.push(ref(derivedType, suffix));
1842
1844
  });
1843
1845
  if (!type.$Abstract) schemas[schemaName].anyOf.push({});
1844
1846
  }
@@ -2186,13 +2188,17 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
2186
2188
 
2187
2189
  if (element[voc.Validation.Maximum] != undefined) {
2188
2190
  if (s.$ref) s = { allOf: [s] };
2189
- s.anyOf[0].maximum = element[voc.Validation.Maximum];
2191
+ if (s.anyOf) {
2192
+ s.anyOf[0].maximum = element[voc.Validation.Maximum];
2193
+ }
2190
2194
  if (element[voc.Validation.Maximum + voc.Validation.Exclusive]) s.exclusiveMaximum = true;
2191
2195
  }
2192
2196
 
2193
2197
  if (element[voc.Validation.Minimum] != undefined) {
2194
2198
  if (s.$ref) s = { allOf: [s] };
2195
- s.anyOf[0].minimum = element[voc.Validation.Minimum];
2199
+ if (s.anyOf) {
2200
+ s.anyOf[0].minimum = element[voc.Validation.Minimum];
2201
+ }
2196
2202
  if (element[voc.Validation.Minimum + voc.Validation.Exclusive]) s.exclusiveMinimum = true;
2197
2203
  }
2198
2204
 
@@ -13,7 +13,7 @@ const messages = {
13
13
  INVALID_AS_OPTION: 'For --as option, please specify one of these output file format [cds, csn, json].',
14
14
  INCORRECT_EXTENSION: 'If --as option is not provided, then please provide either .json or .csn output file extension.',
15
15
  INCORRECT_INPUT_NAMESPACES: 'Invalid value for --include-namespaces option. Specify namespaces as comma-separated list or "*"',
16
- MISSING_ASSOCIATION_SETS: 'Input file provided has missing Association Sets. Please check the edmx file.',
16
+ MISSING_ASSOCIATION: 'Association is missing in the input file provided. Please check the edmx file.',
17
17
  COLLECTION_IN_KEY: 'Key Property of an Entity cannot be of the type Collection.',
18
18
  INVALID_OPENAPI_FILE: 'The OpenAPI file is not valid. Specify the correct OpenAPI file.'
19
19
  };
@@ -57,7 +57,7 @@ class MetadataConverter {
57
57
  * @memberof MetadataConverter
58
58
  * @private
59
59
  */
60
- _filterProperties(input, filter) {
60
+ _filterProperties(input, filter) {
61
61
  const target = {};
62
62
  Object
63
63
  .keys(input)
@@ -466,7 +466,7 @@ class Expression {
466
466
  */
467
467
  if (args.includes('$Scale') && (target.$Type === 'Edm.Decimal' || target.$UnderlyingType === 'Edm.Decimal')) {
468
468
  if (attribs.Scale != null) {
469
- if (attribs.Scale === 'variable' || attribs.Scale === 'floating' ) {
469
+ if (attribs.Scale === 'variable' || attribs.Scale === 'floating') {
470
470
  target.$Scale = attribs.Scale;
471
471
  } else if (attribs.Scale !== '0') {
472
472
  target.$Scale = parseInt(attribs.Scale, 10);
@@ -573,16 +573,16 @@ class Expression {
573
573
  !(target.$Kind === 'NavigationProperty' && target.$Collection === true)) {
574
574
  target.$Nullable = true;
575
575
  }
576
- this.includeExternalAnnotations(element,target);
576
+ this.includeExternalAnnotations(element, target);
577
577
  }
578
- includeExternalAnnotations(element,target){
578
+ includeExternalAnnotations(element, target) {
579
579
  const nonStdAttributes = this.getNonStdAttributes(element.attributes);
580
580
  for (let i = 0; i < nonStdAttributes.length; i++) {
581
581
  let attr = nonStdAttributes[i];
582
582
  let check = 0;
583
583
  let [prefix, attrName] = attr.split(':');
584
584
  if (attrName)
585
- attrName = attrName.replace(/-/g,'.');
585
+ attrName = attrName.replace(/-/g, '.');
586
586
  let attribute;
587
587
  if ((MetadataConverter.custom_namespace.includes(prefix) || MetadataConverter.custom_namespace.includes(true)) && attrName !== undefined)
588
588
  attribute = "@" + prefix + "." + attrName;
@@ -590,12 +590,12 @@ class Expression {
590
590
  attribute = "$" + prefix;
591
591
  if (attribute !== undefined && attribute.startsWith('$') === false) {
592
592
  Object.entries(target).forEach(a => {
593
- if (Object.keys(a[1]).length === 0 && typeof(a[1]) != 'boolean' ) {
593
+ if (Object.keys(a[1]).length === 0 && typeof (a[1]) != 'boolean') {
594
594
  a[1][attribute] = element.attributes[attr];
595
595
  check = 1;
596
596
  }
597
597
  });
598
- if (check == 0 )
598
+ if (check == 0)
599
599
  target[attribute] = element.attributes[attr];
600
600
  }
601
601
  }
@@ -603,8 +603,8 @@ class Expression {
603
603
 
604
604
  getNonStdAttributes(attributes) {
605
605
  const attributeList = [];
606
- const standardArrtibutes = ['MaxLength', 'Precision', 'Scale', 'Nullable', 'DefaultValue',
607
- 'Name', 'Type', 'HasStream', 'IsFlags', 'Alias', 'Namespace', 'BaseType', 'Partner',
606
+ const standardArrtibutes = ['MaxLength', 'Precision', 'Scale', 'Nullable', 'DefaultValue',
607
+ 'Name', 'Type', 'HasStream', 'IsFlags', 'Alias', 'Namespace', 'BaseType', 'Partner',
608
608
  'IsComposable', 'ContainsTarget', 'IsBound', 'EntitySet', 'EntitySetPath', 'EntityType'];
609
609
  attributeList.push(...Object.keys(attributes).filter(attribute => standardArrtibutes.indexOf(attribute) === -1));
610
610
  return attributeList;
@@ -1677,10 +1677,10 @@ class PropertyValueExpression extends Expression {
1677
1677
  this.setTarget(target);
1678
1678
  this.buildTypeCollectionAndFacets('$Type');
1679
1679
 
1680
- if (element.attributes.Path) target[name] = {"=": element.attributes.Path};
1681
- if (element.attributes.PropertyPath) target[name] = {"=": element.attributes.PropertyPath};
1682
- if (element.attributes.EnumMember) target[name] = {"#": element.attributes.EnumMember.substring(element.attributes.EnumMember.lastIndexOf('/')+1)};
1683
- if (element.attributes.NavigationPropertyPath) target[name] = {"=": element.attributes.NavigationPropertyPath};
1680
+ if (element.attributes.Path) target[name] = { "=": element.attributes.Path };
1681
+ if (element.attributes.PropertyPath) target[name] = { "=": element.attributes.PropertyPath };
1682
+ if (element.attributes.EnumMember) target[name] = { "#": element.attributes.EnumMember.substring(element.attributes.EnumMember.lastIndexOf('/') + 1) };
1683
+ if (element.attributes.NavigationPropertyPath) target[name] = { "=": element.attributes.NavigationPropertyPath };
1684
1684
  if (element.attributes.String) target[name] = element.attributes.String;
1685
1685
  if (element.attributes.Bool) target[name] = element.attributes.Bool === 'true';
1686
1686
 
@@ -1918,10 +1918,10 @@ class SchemaExpression extends Expression {
1918
1918
  this.getStack().forEach((expr) => {
1919
1919
  if (['FunctionExpression', 'ActionExpression'].indexOf(expr.getType()) > -1) {
1920
1920
  const operationName = expr.getTargetPropertyName();
1921
- if (target[operationName] != null) {
1921
+ if (target[operationName] && Array.isArray(target[operationName])) {
1922
1922
  const operation = expr.interpret()[operationName];
1923
1923
  target[operationName].push(...operation);
1924
- } else {
1924
+ } else if (!target[operationName]) {
1925
1925
  Object.assign(target, expr.interpret());
1926
1926
  }
1927
1927
  } else if (expr.getType() === 'AnnotationsExpression') {
@@ -2236,10 +2236,10 @@ class EntityContainerExpression extends Expression {
2236
2236
  interpret() {
2237
2237
  const element = this.getElement();
2238
2238
  const target = { $Kind: 'EntityContainer' };
2239
- this.includeExternalAnnotations(element,target);
2239
+ this.includeExternalAnnotations(element, target);
2240
2240
  this.setTarget(target);
2241
2241
  super.interpret();
2242
- return { [element.attributes.Name]: target};
2242
+ return { [element.attributes.Name]: target };
2243
2243
  }
2244
2244
  }
2245
2245
 
@@ -2254,6 +2254,12 @@ class EntitySetExpression extends Expression {
2254
2254
  if (this._kind === 'EntitySet') {
2255
2255
  target.$Collection = true;
2256
2256
  }
2257
+ if (this._kind === 'Singleton') {
2258
+ target.$Singleton = true;
2259
+ if (element.attributes.Nullable === 'true') {
2260
+ target.$SingletonNullable = true;
2261
+ }
2262
+ }
2257
2263
  this.setTarget(target);
2258
2264
  this.buildTypeCollectionAndFacets('$Type');
2259
2265