@sap/cds-dk 4.6.4 → 4.8.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 (74) hide show
  1. package/CHANGELOG.md +68 -1
  2. package/bin/activate.js +5 -1
  3. package/bin/add.js +2 -1
  4. package/bin/bind.js +121 -48
  5. package/bin/cds.js +8 -0
  6. package/bin/env.js +0 -2
  7. package/bin/repl.js +5 -2
  8. package/bin/watch.js +10 -7
  9. package/lib/bind/bindingResolver.js +26 -14
  10. package/lib/bind/bindingUtil.js +144 -0
  11. package/lib/bind/cfKeyProvider.js +2 -80
  12. package/lib/bind/kubernetesCredentialsProvider.js +157 -0
  13. package/lib/bind/userCdsrcJsonStorageProvider.js +7 -7
  14. package/lib/client/activate.js +16 -13
  15. package/lib/client/base_command.js +5 -1
  16. package/lib/client/helper/cf_helper.js +5 -2
  17. package/lib/client/params.js +16 -2
  18. package/lib/client/settings_manager.js +15 -8
  19. package/lib/compile/openapi/csdl2openapi.js +6 -8
  20. package/lib/compile/to-xsuaa.js +0 -14
  21. package/lib/import/utils/MetadataConverter.js +6 -4
  22. package/lib/import/utils/MultiMap.js +1 -0
  23. package/lib/import/v2parser.js +150 -64
  24. package/lib/import/v4parser.js +118 -56
  25. package/lib/index.js +6 -5
  26. package/lib/init/constants.js +11 -5
  27. package/lib/init/index.js +14 -7
  28. package/lib/init/template/_merging/existences.js +161 -0
  29. package/lib/init/template/approuter/files/default-env.json +9 -0
  30. package/lib/init/template/approuter/files/mta.yaml.hbs +48 -0
  31. package/lib/init/template/approuter/files/package.json +9 -0
  32. package/lib/init/template/approuter/files/xs-app.json +8 -0
  33. package/lib/init/template/approuter/index.js +66 -0
  34. package/lib/init/template/auditlog/files/dependencies.package.json +5 -0
  35. package/lib/init/template/auditlog/files/mta.yaml.hbs +13 -0
  36. package/lib/init/template/auditlog/index.js +38 -0
  37. package/lib/init/template/cf-manifest/index.js +1 -1
  38. package/lib/init/template/data/index.js +18 -6
  39. package/lib/init/template/hana/files/common/undeploy.json +5 -0
  40. package/lib/init/template/hana/files/java/package.json +3 -0
  41. package/lib/init/template/hana/files/mta.yaml.hbs +42 -0
  42. package/lib/init/template/hana/files/nodejs/dependencies.package.json +5 -0
  43. package/lib/init/template/hana/index.js +53 -24
  44. package/lib/init/template/java/index.js +1 -5
  45. package/lib/init/template/lint/index.js +17 -1
  46. package/lib/init/template/mta/files/mta.yaml.hbs +27 -216
  47. package/lib/init/template/mta/index.js +25 -17
  48. package/lib/init/template/mtx/files/cds.package.json +24 -0
  49. package/lib/init/template/mtx/files/dependencies.package.json +5 -0
  50. package/lib/init/template/mtx/files/mta.yaml.hbs +74 -0
  51. package/lib/init/template/mtx/files/xs-security.json.hbs +23 -9
  52. package/lib/init/template/mtx/index.js +126 -29
  53. package/lib/init/template/templateBase.js +14 -0
  54. package/lib/init/template/xsuaa/files/cds.package.json +9 -0
  55. package/lib/init/template/xsuaa/files/dependencies.package.json +6 -0
  56. package/lib/init/template/xsuaa/files/mta.yaml.hbs +17 -0
  57. package/lib/init/template/xsuaa/index.js +75 -0
  58. package/lib/init/util/fsUtil.js +18 -4
  59. package/lib/init/util/logger.js +3 -1
  60. package/lib/init/util/mvnArchetypeUtil.js +3 -1
  61. package/lib/init/{template/mta → util}/projectReader.js +26 -11
  62. package/lib/init/util/templateUtil.js +267 -3
  63. package/lib/lint/Linter.js +1 -2
  64. package/lib/lint/io.js +3 -12
  65. package/lib/util/exec.d.ts +54 -0
  66. package/lib/util/exec.js +210 -0
  67. package/lib/watch/livereload-server.js +43 -31
  68. package/lib/watch/node-watch.js +46 -20
  69. package/lib/watch/open.js +1 -1
  70. package/npm-shrinkwrap.json +3382 -9106
  71. package/package.json +8 -9
  72. package/readme.md +1 -1
  73. package/lib/init/template/mtx/package/cds.package.json +0 -29
  74. package/lib/init/template/mtx/package/dependencies.package.json +0 -9
@@ -8,18 +8,6 @@ const { URL } = require('url');
8
8
  const execAsync = util.promisify(cp.exec);
9
9
  const IS_WIN = os.platform() === 'win32';
10
10
 
11
- const KindToRequiresNameMap = {
12
- xsuaa: 'uaa',
13
- hana: 'db',
14
- destinations: 'destinations',
15
- connectivity: 'connectivity',
16
- 'audit-log': 'audit-log',
17
- 'enterprise-messaging': 'messaging',
18
- 'enterprise-messaging-amqp': 'messaging',
19
- 'enterprise-messaging-http': 'messaging',
20
- 'enterprise-messaging-shared': 'messaging'
21
- }
22
-
23
11
  class CFKeyProvider {
24
12
 
25
13
  constructor() {
@@ -69,7 +57,6 @@ class CFKeyProvider {
69
57
 
70
58
  async getCfTargetFromCli() {
71
59
  const result = await this._cfRead('target');
72
-
73
60
  return {
74
61
  apiEndpoint: this.extract(result, /^api endpoint:\s+(.*)$/i),
75
62
  user: this.extract(result, /^user:\s+(.*)$/i),
@@ -142,7 +129,7 @@ class CFKeyProvider {
142
129
  name = name || `${instance}:${key}`;
143
130
 
144
131
  if (apiEndpoint && apiEndpoint !== target.apiEndpoint) {
145
- throw new Error(`Current Cloud Foundry api endpoint "${target.apiEndpoint}" doesn't match with requested api endpoint "${apiEndpoint}" for service binding "${name}".`);
132
+ throw new Error(`Current Cloud Foundry api endpoint "${target.apiEndpoint}" different than api endpoint "${apiEndpoint}" for service binding "${name}".`);
146
133
  }
147
134
 
148
135
  let spaceGuid;
@@ -182,6 +169,7 @@ class CFKeyProvider {
182
169
  vcap: {
183
170
  label: offeringObj.name,
184
171
  plan: planObj.name,
172
+ tags: offeringObj.tags
185
173
  },
186
174
  resolved: true,
187
175
  },
@@ -190,72 +178,6 @@ class CFKeyProvider {
190
178
 
191
179
  return resolvedBinding;
192
180
  }
193
-
194
- getRequiresNameFromKind(service, kind) {
195
- const requiresName = KindToRequiresNameMap[kind];
196
- if (!requiresName) throw new Error(`Unknown CDS service name for service "${service}". Please specify as argument for "cds bind".`)
197
- return requiresName;
198
- }
199
-
200
- /**
201
- * Determine kind value for services based on VCAP binding
202
- * information in the configuration.
203
- */
204
- determineKinds(services) {
205
- const vcapServices = {}
206
-
207
- const getIndex = service => {
208
- const { credentials } = service;
209
- return credentials && credentials.binding && credentials.binding.index;
210
- }
211
-
212
- // Build VCAP_SERVICES
213
- for (let index = 0; index<services.length; index++) {
214
- const service = services[index];
215
- const { label, name, tags } = service.binding.vcap;
216
- if (!vcapServices[label]) vcapServices[label] = [];
217
- vcapServices[label].push({ credentials: { binding: { index } }, label, name, tags });
218
-
219
- service.kindCandidates = [ ]
220
- service.nameCandidates = [ ]
221
- }
222
-
223
- // Auto configure services based on VCAP_SERVICES
224
- const cds = require('@sap/cds/lib');
225
- const VCAP_SERVICES = process.env.VCAP_SERVICES;
226
- try {
227
- process.env.VCAP_SERVICES = JSON.stringify(vcapServices);
228
- const env = cds.env.for('cds', '.');
229
-
230
- // Extract "kind" from auto configured services
231
- for (const kind of Object.keys(env.requires)) {
232
- const requireService = env.requires[kind];
233
- const superKind = requireService.kind;
234
- const index = getIndex(requireService);
235
- if (!Number.isInteger(index)) continue;
236
-
237
- const superRequireService = env.requires[superKind];
238
- if (superRequireService) {
239
- const superIndex = getIndex(superRequireService);
240
- if (index === superIndex) continue;
241
- }
242
-
243
- services[index].kindCandidates.push(kind);
244
- }
245
-
246
- // Check if one "kind" per service was found
247
- for (const service of services) {
248
- if (service.kindCandidates.length !== 1) {
249
- throw new Error(`Unknown kind for service "${service.binding.instance}". Please specify with "--kind" option.`);
250
- }
251
-
252
- service.kind = service.kindCandidates[0];
253
- delete service.kindCandidates;
254
- }
255
- } finally {
256
- process.env.VCAP_SERVICES = VCAP_SERVICES;
257
- }
258
- }
259
181
  }
260
182
 
261
183
  class BatchRequest {
@@ -0,0 +1,157 @@
1
+ const cp = require('child_process');
2
+ const os = require('os');
3
+ const util = require('util');
4
+ const yaml = require("@sap/cds-foss")("yaml");
5
+
6
+ const axios = require('axios');
7
+ const { URL } = require('url');
8
+
9
+ const execFile = util.promisify(cp.execFile);
10
+ const IS_WIN = os.platform() === 'win32';
11
+
12
+ class KubernetesCredentialsProvider {
13
+
14
+ constructor() {
15
+ this.logger = console;
16
+ }
17
+
18
+ setLogger(logger) {
19
+ this.logger = logger;
20
+ }
21
+
22
+ async _kubectlRead(...args) {
23
+ this.logger.debug(`_kubectlRead: kubectl ${args.join(" ")}`);
24
+
25
+ const result = await execFile("kubectl", args, {
26
+ shell: IS_WIN,
27
+ stdio: ['inherit', 'pipe', 'inherit'],
28
+ });
29
+
30
+ return result.stdout.toString().trim();
31
+ }
32
+
33
+
34
+ async _kubectlConfig() {
35
+ const configString = await this._kubectlRead('config', 'view', '--raw');
36
+ const config = yaml.parse(configString);
37
+
38
+ const currentContextName = config["current-context"];
39
+ if (!currentContextName) throw new Error(`No current context in kubectl config.`);
40
+ const currentContextEntry = config.contexts && config.contexts.find( entry => entry.name === currentContextName && entry.context && entry.context.cluster && entry.context.user );
41
+ if (!currentContextEntry) throw new Error(`Current context not found in kubectl config.`);
42
+ const currentContext = currentContextEntry.context;
43
+ const clusterEntry = config.clusters && config.clusters.find( entry => entry.name === currentContext.cluster && entry.cluster && entry.cluster.server );
44
+ if (!currentContextEntry) throw new Error(`Cluster ${currentContext.cluster} not found in kubectl config.`);
45
+ const cluster = clusterEntry.cluster;
46
+ const userEntry = config.users && config.users.find( entry => entry.name === currentContext.user && entry.user && entry.user.token );
47
+ if (!userEntry) throw new Error(`User ${currentContext.user} not found in kubectl config.`);
48
+ const user = userEntry.user;
49
+
50
+ return {
51
+ url: cluster.server,
52
+ namespace: currentContext.namespace || 'default',
53
+ token: user.token
54
+ }
55
+ }
56
+
57
+ async init() {
58
+ this.config = await this._kubectlConfig();
59
+ }
60
+
61
+ async _req(urlPath) {
62
+
63
+ const url = new URL(urlPath, this.config.url);
64
+ this.logger.debug(`_req: ${url.toString()}`);
65
+
66
+ const result = await axios.get(url.toString(), {
67
+ validateStatus: status => status === 200 || status === 404,
68
+ headers: {
69
+ authorization: `Bearer ${this.config.token}`
70
+ }
71
+ });
72
+
73
+ if (result.status === 200) {
74
+ return result.data;
75
+ } else {
76
+ return null
77
+ }
78
+ }
79
+
80
+
81
+ async resolve(name, binding) {
82
+ if (!this.initPromise) {
83
+ this.initPromise = this.init();
84
+ }
85
+ await this.initPromise;
86
+
87
+ let {cluster, namespace, name: bindingName, secret } = binding;
88
+ let instance;
89
+ let secretOrBindingResource = 'Secret';
90
+
91
+ if (!namespace) namespace = this.config.namespace;
92
+ if (cluster && cluster !== this.config.url) throw new Error(`Current Kubernetes cluster "${this.config.url}" different than Kubernetes cluster "${cluster}" for service binding "${name}".`);
93
+
94
+ if (bindingName) {
95
+ const bindingResource = await this._req(`/apis/servicecatalog.k8s.io/v1beta1/namespaces/${encodeURIComponent(namespace)}/servicebindings/${encodeURIComponent(bindingName)}`);
96
+ if (bindingResource === null) {
97
+ // Fallback to secret instead of service binding;
98
+ secret = bindingName;
99
+ bindingName = undefined;
100
+ secretOrBindingResource = 'Service Binding or Secret';
101
+ } else {
102
+ secret = bindingResource.spec.secretName || throwError();
103
+ instance = bindingResource.spec.instanceRef && bindingResource.spec.instanceRef.name || throwError();
104
+ }
105
+ }
106
+
107
+ const secretResource = await this._req(`/api/v1/namespaces/${encodeURIComponent(namespace)}/secrets/${encodeURIComponent(secret)}`);
108
+ if (secretResource === null) throwError(`${secretOrBindingResource} '${secret}' not found.`);
109
+
110
+ const credentials = {};
111
+ for (const name in secretResource.data) {
112
+ let value = secretResource.data[name];
113
+ value = Buffer.from(value, "base64").toString(); // secret values are base64 encoded
114
+ // TODO: TESTIT
115
+ if (value && value[0] === '{') try { value = JSON.parse(value); } catch(e) {/* ignored */} // secret values may contain JSON objects
116
+ credentials[name] = value;
117
+ }
118
+
119
+ const resolvedBinding = {
120
+ binding: {
121
+ type: "k8s",
122
+ name: bindingName,
123
+ cluster: this.config.url,
124
+ instance,
125
+ namespace,
126
+ secret: secret,
127
+ resolved: true,
128
+ },
129
+ credentials
130
+ };
131
+
132
+
133
+ if (instance) {
134
+ const serviceInstanceResource = await this._req(`/apis/servicecatalog.k8s.io/v1beta1/namespaces/${encodeURIComponent(namespace)}/serviceinstances/${encodeURIComponent(instance)}`);
135
+
136
+ resolvedBinding.binding.vcap = {
137
+ label: serviceInstanceResource.spec.clusterServiceClassExternalName,
138
+ plan: serviceInstanceResource.spec.clusterServicePlanExternalName
139
+ }
140
+ }
141
+
142
+ return resolvedBinding;
143
+ }
144
+
145
+ }
146
+
147
+
148
+ /**
149
+ *
150
+ * @param {string} msg
151
+ * @returns {never}
152
+ */
153
+ function throwError(msg = 'Internal Error') {
154
+ throw new Error(msg);
155
+ }
156
+
157
+ module.exports = new KubernetesCredentialsProvider();
@@ -3,7 +3,7 @@ const path = require('path');
3
3
  const readline = require('readline');
4
4
 
5
5
  const { fse } = require('@sap/cds-foss');
6
- const { highlight, warn } = require('../util/term');
6
+ const { highlight } = require('../util/term');
7
7
 
8
8
  const commandUtil = require('../init/util/commandUtil');
9
9
 
@@ -108,19 +108,19 @@ ${file}
108
108
  }
109
109
 
110
110
  for (const service of services) {
111
- const { name, kind, binding, credentials } = service;
111
+ const { name, kind, binding, credentials, vcap } = service;
112
112
  const requireService = section[name] = section[name] || {};
113
113
 
114
- Object.assign(requireService, { kind, binding, credentials });
115
-
116
- options.logger.log(`Binding ${highlight(name)} to Cloud Foundry service ${highlight(binding.instance)} key ${highlight(binding.key)} with kind ${highlight(kind)}.`);
114
+ Object.assign(requireService, { binding, credentials });
115
+ if (kind) requireService.kind = kind;
116
+ if (vcap) requireService.vcap = vcap;
117
117
  }
118
118
 
119
119
  await this.writeJsonFile(jsonPath, jsonObject, options.logger);
120
120
  if (options.profile) {
121
- options.logger.log(`Saving bindings to ${highlight(jsonPath)} in profile ${highlight(options.profile)} .`);
121
+ options.logger.log(`Saving bindings to ${highlight(jsonPath)} in profile ${highlight(options.profile)}`);
122
122
  } else {
123
- options.logger.log(`Saving bindings to ${highlight(jsonPath)}.`);
123
+ options.logger.log(`Saving bindings to ${highlight(jsonPath)}`);
124
124
  }
125
125
  }
126
126
 
@@ -8,6 +8,7 @@ const { collectFileContent, collectFiles, toUnixPath } = require('./helper/fs_he
8
8
  const Logger = require('./helper/logging');
9
9
 
10
10
  const COMMAND_URL_SUFFIX = 'asyncActivate';
11
+ const COMMAND_URL_SUFFIX_SYNC = 'activate';
11
12
  const UPDATE_CUSTOM_FILE_URL_SUFFIX = 'updateCustomTenantContent';
12
13
 
13
14
  const I18N_WHITELIST = ['.properties', '.csv'];
@@ -36,6 +37,7 @@ async function checkFinished(jobId, statusUrl, params, finisher) {
36
37
  module.exports = class Activate extends BaseCommand {
37
38
 
38
39
  static get COMMAND_URL_SUFFIX() { return COMMAND_URL_SUFFIX; }
40
+ static get COMMAND_URL_SUFFIX_SYNC() { return COMMAND_URL_SUFFIX_SYNC; }
39
41
  static get UPDATE_CUSTOM_FILE_URL_SUFFIX() { return UPDATE_CUSTOM_FILE_URL_SUFFIX; }
40
42
 
41
43
  static async run(injection, paramValues) {
@@ -46,7 +48,8 @@ module.exports = class Activate extends BaseCommand {
46
48
  Logger.log('Activating extension with parameters ' + JSON.stringify({
47
49
  appUrl: params.get('appUrl'),
48
50
  projectFolder: params.get('projectFolder'),
49
- undeploy: params.get('undeploy')
51
+ undeploy: params.get('undeploy'),
52
+ sync: params.get('sync')
50
53
  }));
51
54
  } else {
52
55
  Logger.log(`Activating extension located at ${params.get('projectFolder')} to URL ${params.get('appUrl')}`);
@@ -155,6 +158,7 @@ module.exports = class Activate extends BaseCommand {
155
158
 
156
159
  static async runActivate(cds, params) {
157
160
  let activationUrl;
161
+ const isSync = params.get('sync');
158
162
 
159
163
  return Activate.collectExtensionFiles(cds, params.get('projectFolder'))
160
164
  .then(allFiles => {
@@ -164,20 +168,20 @@ module.exports = class Activate extends BaseCommand {
164
168
 
165
169
  Logger.log('Activating extension...');
166
170
 
167
- activationUrl = Activate.getMtxApiUrl(params.get('appUrl'), COMMAND_URL_SUFFIX);
171
+ const urlSuffix = isSync ? Activate.COMMAND_URL_SUFFIX_SYNC : Activate.COMMAND_URL_SUFFIX;
172
+ activationUrl = Activate.getMtxApiUrl(params.get('appUrl'), urlSuffix);
168
173
  const requestOptions = Object.assign({
169
- headers: {},
170
- data: {
171
- extension: [...allFiles],
172
- undeployExtension: params.get('undeploy')
173
- }
174
+ headers: {}
174
175
  }, params.get('reqAuth'));
175
176
  requestOptions.headers['Content-Type'] = 'application/json';
176
177
 
177
- return axios.post(activationUrl, requestOptions);
178
+ return axios.post(activationUrl, {
179
+ extension: [...allFiles],
180
+ undeployExtension: params.get('undeploy')
181
+ }, requestOptions);
178
182
  })
179
183
  .then(response => {
180
- if (response === null) {
184
+ if (isSync || response === null) {
181
185
  return;
182
186
  }
183
187
 
@@ -223,13 +227,12 @@ module.exports = class Activate extends BaseCommand {
223
227
  const customFileUrl = Activate.getMtxApiUrl(params.get('appUrl'), Activate.UPDATE_CUSTOM_FILE_URL_SUFFIX);
224
228
  const requestOptions = Object.assign({
225
229
  headers: {},
226
- data: {
227
- content: customFilesSerialized
228
- }
229
230
  }, params.get('reqAuth'));
230
231
  requestOptions.headers['Content-Type'] = 'application/json';
231
232
 
232
- await axios.post(customFileUrl, requestOptions);
233
+ await axios.post(customFileUrl, {
234
+ content: customFilesSerialized
235
+ }, requestOptions);
233
236
 
234
237
  Logger.log('Custom file upload finished');
235
238
  }
@@ -19,7 +19,7 @@ module.exports = class BaseCommand {
19
19
  throw error;
20
20
  }
21
21
 
22
- switch (error.response.status) {
22
+ switch (error.response && error.response.status) {
23
23
  case 401:
24
24
  Logger.error('Request failed: authorization might have expired. Please retry with valid passcode' +
25
25
  (params.has('passcodeUrl') ? ` from ${params.get('passcodeUrl')}` : ''), error);
@@ -52,4 +52,8 @@ module.exports = class BaseCommand {
52
52
  static getMtxApiUrl(url, action) {
53
53
  return BaseCommand.concatUrls(url, MTX_URL_PATH_PREFIX, action);
54
54
  }
55
+
56
+ static getMtxApiUrlSync(url, action) {
57
+ return BaseCommand.concatUrls(url, MTX_URL_PATH_PREFIX, action);
58
+ }
55
59
  }
@@ -154,12 +154,13 @@ async function getSubdomain(appName) {
154
154
  }
155
155
  }
156
156
 
157
+ Logger.debug(`Subdomain determined from CF app environment: ${value}`);
157
158
  return resolve(value);
158
159
  });
159
160
  });
160
161
 
161
162
  function errorResult(message) {
162
- Logger.debug(message);
163
+ Logger.debug(`Failed to get subdomain: ${message}`);
163
164
  return undefined;
164
165
  }
165
166
  }
@@ -174,8 +175,10 @@ async function getAppName(appUrl) {
174
175
  const app = apps.find(app => equalIgnoringTls(app.url, appUrl));
175
176
  if (!app) {
176
177
  Logger.debug(`Failed to find app with URL ${appUrl}. Running apps: ${apps.map(app => app.name)}`);
178
+ return;
177
179
  }
178
- return app && app.name;
180
+ Logger.debug(`App name determined from CF: ${app.name}`);
181
+ return app.name;
179
182
  }
180
183
 
181
184
  module.exports = {
@@ -20,7 +20,7 @@ const ObfuscationLevel = Object.freeze({
20
20
  full: 1,
21
21
  partial: 2 // Allows a few start and end characters displayed in DEBUG output; only use with string-type params
22
22
  });
23
- const DISCLOSURE_LENGTH = 3;
23
+ const DISCLOSURE_LENGTH = 2;
24
24
 
25
25
  /**
26
26
  * Parameters hold a name and a value and can only be set to compatible values.
@@ -205,6 +205,7 @@ class AllParams extends BasicParams {
205
205
  // `activate` PARAMS
206
206
  this.undeploy = new Param('undeploy', false);
207
207
  this.wsdl = new Param('wsdl', false);
208
+ this.sync = new Param('sync', false);
208
209
 
209
210
  // `logout` PARAMS
210
211
  this.deleteSettings = new Param('deleteSettings', false);
@@ -331,7 +332,7 @@ class ParamCollection {
331
332
  }
332
333
 
333
334
  /**
334
- * Add all params contained in another collection to this.
335
+ * Add all params contained in another collection to this, replacing existing params.
335
336
  * @param params the other `ParamCollection`
336
337
  */
337
338
  merge(params) {
@@ -341,6 +342,19 @@ class ParamCollection {
341
342
  }
342
343
  }
343
344
 
345
+ /**
346
+ * Add all params contained in another collection to this, not replacing existing params.
347
+ * @param params the other `ParamCollection`
348
+ */
349
+ mergeLower(params) {
350
+ this.checkFrozen();
351
+ for (const param of params) {
352
+ if (!this.has(param.name)) {
353
+ this.set(param.name, param.val);
354
+ }
355
+ }
356
+ }
357
+
344
358
  /**
345
359
  * Iterate through all contained param objects.
346
360
  */
@@ -71,13 +71,18 @@ async function getAppUrlAndSubdomainFromSuggestions(params) {
71
71
  params.set('appUrl', app.url);
72
72
 
73
73
  if (!params.has('subdomain')) {
74
- params.set('subdomain', await getSubdomain(app.name));
75
- if (params.has('subdomain')) {
76
- Logger.log(`Subdomain determined from CF app environment: ${params.get('subdomain')}`);
77
- } else {
78
- Logger.log('Failed to determine subdomain from CF app environment');
79
- }
74
+ params.set('subdomain', await getSubdomainFromCfAndNotify(app.name));
75
+ }
76
+ }
77
+
78
+ async function getSubdomainFromCfAndNotify(appName) {
79
+ const subdomain = await getSubdomain(appName);
80
+ if (subdomain) {
81
+ Logger.log(`Subdomain determined from CF app environment: ${subdomain}`);
82
+ } else {
83
+ Logger.log('Failed to determine subdomain from CF app environment');
80
84
  }
85
+ return subdomain;
81
86
  }
82
87
 
83
88
  function getTokenBaseUrl(params, renewUrl) {
@@ -156,7 +161,7 @@ class SettingsManager {
156
161
  const loadedSettings = await this._loadFromFile(projectFolder);
157
162
  Logger.debug(`Loaded project settings: ${loadedSettings.format()}`);
158
163
 
159
- params.merge(loadedSettings);
164
+ params.mergeLower(loadedSettings);
160
165
 
161
166
  await this.updateUrls(params, logout);
162
167
  await this.checkKeytar(params, logout);
@@ -172,12 +177,13 @@ class SettingsManager {
172
177
  }
173
178
  if (!logout) {
174
179
  if (!params.has('appUrl')) {
180
+ Logger.debug('App URL not given');
175
181
  await getAppUrlAndSubdomainFromSuggestions(params);
176
182
  } else if (!params.has('subdomain')) {
177
183
  Logger.debug('Subdomain not given');
178
184
  const appName = await getAppName(params.get('appUrl'));
179
185
  if (appName) {
180
- params.set('subdomain', await getSubdomain(appName));
186
+ params.set('subdomain', await getSubdomainFromCfAndNotify(appName));
181
187
  }
182
188
  }
183
189
  }
@@ -189,6 +195,7 @@ class SettingsManager {
189
195
  if (!params.has('subdomain')) {
190
196
  throw new Error('Subdomain not given');
191
197
  }
198
+ Logger.log(`Subdomain determined from app URL: ${params.get('subdomain')}`);
192
199
  }
193
200
  }
194
201
  function renewTokenUrl() {
@@ -1,7 +1,5 @@
1
1
  /**
2
2
  * Converts OData CSDL JSON to OpenAPI 3.0.2
3
- *
4
- * Latest version: https://github.wdf.sap.corp/d037427/odata-openapi/blob/master/lib/csdl2openapi.js
5
3
  */
6
4
 
7
5
  var pluralize = require('pluralize')
@@ -776,7 +774,7 @@ module.exports.csdl2openapi = function (
776
774
 
777
775
  /**
778
776
  * Unpack EnumMember value if it uses CSDL JSON CS01 style, like CAP does
779
- * @param {string or object} path Qualified name of referenced type
777
+ * @param {string or object} path Qualified name of referenced type
780
778
  * @return {object} Reference Object
781
779
  */
782
780
  function enumMember(member) {
@@ -788,7 +786,7 @@ module.exports.csdl2openapi = function (
788
786
 
789
787
  /**
790
788
  * Unpack NavigationPropertyPath value if it uses CSDL JSON CS01 style, like CAP does
791
- * @param {string or object} path Qualified name of referenced type
789
+ * @param {string or object} path Qualified name of referenced type
792
790
  * @return {object} Reference Object
793
791
  */
794
792
  function navigationPropertyPath(path) {
@@ -800,7 +798,7 @@ module.exports.csdl2openapi = function (
800
798
 
801
799
  /**
802
800
  * Unpack PropertyPath value if it uses CSDL JSON CS01 style, like CAP does
803
- * @param {string or object} path Qualified name of referenced type
801
+ * @param {string or object} path Qualified name of referenced type
804
802
  * @return {object} Reference Object
805
803
  */
806
804
  function propertyPath(path) {
@@ -2147,7 +2145,7 @@ module.exports.csdl2openapi = function (
2147
2145
 
2148
2146
  /**
2149
2147
  * Augment OpenAPI document with Security Requirements Object
2150
- * @param {object} openapi OpenAPI document to augment
2148
+ * @param {object} openapi OpenAPI document to augment
2151
2149
  * @param {object} entityContainer Entity Container object
2152
2150
  */
2153
2151
  function security(openapi, entityContainer) {
@@ -2162,7 +2160,7 @@ module.exports.csdl2openapi = function (
2162
2160
 
2163
2161
  /**
2164
2162
  * a qualified name consists of a namespace or alias, a dot, and a simple name
2165
- * @param {string} qualifiedName
2163
+ * @param {string} qualifiedName
2166
2164
  * @return {string} namespace-qualified name
2167
2165
  */
2168
2166
  function namespaceQualifiedName(qualifiedName) {
@@ -2172,7 +2170,7 @@ module.exports.csdl2openapi = function (
2172
2170
 
2173
2171
  /**
2174
2172
  * a qualified name consists of a namespace or alias, a dot, and a simple name
2175
- * @param {string} qualifiedName
2173
+ * @param {string} qualifiedName
2176
2174
  * @return {object} with components qualifier and name
2177
2175
  */
2178
2176
  function nameParts(qualifiedName) {
@@ -107,8 +107,6 @@ module.exports = function compile_to_xsuaa (model, options) {
107
107
 
108
108
  function mergeConfig(base, generated) {
109
109
  const result = base
110
- result.xsappname = base.xsappname || appName()
111
- result['tenant-mode'] = base['tenant-mode'] || 'dedicated'
112
110
 
113
111
  const mergeByName = (type) => {
114
112
  if (!generated[type]) return
@@ -140,15 +138,3 @@ function getBaseModel(options = {}) {
140
138
  }
141
139
  return options.base || {}
142
140
  }
143
-
144
- function appName() {
145
- const cwd = cds.root || process.cwd()
146
- let name
147
- try {
148
- name = require(path.resolve(cwd,'package.json')).name
149
- } catch (err) { // no package.json
150
- name = path.basename(cwd)
151
- }
152
- // remove any @foo/ from @foo/bar, truncate to max length
153
- return name.split('/').pop().substring(0, 100)
154
- }
@@ -396,7 +396,6 @@ class Expression {
396
396
  const target = this.getTarget();
397
397
  const element = this.getElement();
398
398
  const attribs = element.attributes;
399
- let custom_namespace = MetadataConverter.custom_namespace;
400
399
  if (attribs == null) return;
401
400
  let realType = attribs.Type || attribs.EntityType || attribs.UnderlyingType;
402
401
  const propertyName = attribs.UnderlyingType == null ? '$Type' : '$UnderlyingType';
@@ -574,6 +573,9 @@ class Expression {
574
573
  !(target.$Kind === 'NavigationProperty' && target.$Collection === true)) {
575
574
  target.$Nullable = true;
576
575
  }
576
+ this.includeExternalAnnotations(element,target);
577
+ }
578
+ includeExternalAnnotations(element,target){
577
579
  const nonStdAttributes = this.getNonStdAttributes(element.attributes);
578
580
  for (let i = 0; i < nonStdAttributes.length; i++) {
579
581
  let attr = nonStdAttributes[i];
@@ -581,7 +583,7 @@ class Expression {
581
583
  if(attrName)
582
584
  attrName = attrName.replace(/-/g,'.');
583
585
  let attribute;
584
- if ((custom_namespace.includes(prefix) && attrName !== undefined) || custom_namespace.includes(true))
586
+ if ((MetadataConverter.custom_namespace.includes(prefix) && attrName !== undefined) || MetadataConverter.custom_namespace.includes(true))
585
587
  attribute = "@" + prefix + "." + attrName;
586
588
  else if (attrName === undefined)
587
589
  attribute = "$" + prefix;
@@ -2221,10 +2223,10 @@ class EntityContainerExpression extends Expression {
2221
2223
  interpret() {
2222
2224
  const element = this.getElement();
2223
2225
  const target = { $Kind: 'EntityContainer' };
2226
+ this.includeExternalAnnotations(element,target);
2224
2227
  this.setTarget(target);
2225
2228
  super.interpret();
2226
-
2227
- return { [element.attributes.Name]: target };
2229
+ return { [element.attributes.Name]: target};
2228
2230
  }
2229
2231
  }
2230
2232
 
@@ -49,6 +49,7 @@ var Multimap = (function () {
49
49
  * @param {Object} key
50
50
  * @param {Object} val...
51
51
  */
52
+ // eslint-disable-next-line no-unused-vars
52
53
  Multimap.prototype.set = function (key, val) {
53
54
  var args = Array.prototype.slice.call(arguments);
54
55