@sap/cds-dk 4.6.0 → 4.7.1

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 (43) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/bin/add.js +2 -1
  3. package/bin/bind.js +121 -48
  4. package/bin/cds.js +8 -0
  5. package/bin/compile/index.js +2 -1
  6. package/bin/env.js +0 -2
  7. package/bin/repl.js +11 -4
  8. package/bin/watch.js +25 -13
  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 +9 -17
  15. package/lib/client/auth_manager.js +1 -1
  16. package/lib/client/extend.js +4 -9
  17. package/lib/client/settings_manager.js +5 -4
  18. package/lib/import/index.js +3 -2
  19. package/lib/import/internalDoc/edm2cdsTypeMapping.md +38 -0
  20. package/lib/import/utils/MultiMap.js +1 -0
  21. package/lib/import/v2parser.js +102 -37
  22. package/lib/import/v4parser.js +59 -20
  23. package/lib/index.js +6 -5
  24. package/lib/init/constants.js +2 -2
  25. package/lib/init/index.js +1 -0
  26. package/lib/init/template/data/index.js +29 -7
  27. package/lib/init/template/hana/files/common/undeploy.json +5 -0
  28. package/lib/init/template/hana/files/java/package.json +3 -0
  29. package/lib/init/template/hana/index.js +19 -23
  30. package/lib/init/template/lint/index.js +28 -18
  31. package/lib/init/template/mta/files/mta.yaml.hbs +12 -5
  32. package/lib/init/template/mta/projectReader.js +1 -3
  33. package/lib/init/template/nodejs/files/.gitignore.hbs +0 -2
  34. package/lib/init/template/samples/index.js +1 -1
  35. package/lib/init/template/templateBase.js +10 -41
  36. package/lib/init/util/fsUtil.js +1 -1
  37. package/lib/lint/Linter.js +13 -33
  38. package/lib/lint/checks.js +1 -5
  39. package/lib/lint/generators.js +5 -4
  40. package/lib/lint/io.js +23 -31
  41. package/lib/watch/node-watch.js +46 -20
  42. package/npm-shrinkwrap.json +11524 -5622
  43. package/package.json +3 -3
@@ -0,0 +1,144 @@
1
+ const KindToRequiresNameMap = {
2
+ xsuaa: 'uaa',
3
+ hana: 'db',
4
+ destinations: 'destinations',
5
+ connectivity: 'connectivity',
6
+ 'audit-log': 'audit-log',
7
+ 'enterprise-messaging': 'messaging',
8
+ 'enterprise-messaging-amqp': 'messaging',
9
+ 'enterprise-messaging-http': 'messaging',
10
+ 'enterprise-messaging-shared': 'messaging'
11
+ }
12
+
13
+ function getRequiresNameFromKind(service, kind) {
14
+ const requiresName = KindToRequiresNameMap[kind];
15
+ if (!requiresName) throw new Error(`Unknown CDS service name for service "${service}". Please specify as argument for "cds bind".`)
16
+ return requiresName;
17
+ }
18
+
19
+ /**
20
+ * Determine kind value for services based on VCAP binding
21
+ * information in the configuration.
22
+ */
23
+ function determineKinds(services) {
24
+ const vcapServices = {}
25
+
26
+ const getIndex = service => {
27
+ const { credentials } = service;
28
+ return credentials && credentials.binding && credentials.binding.index;
29
+ }
30
+
31
+ // Build VCAP_SERVICES
32
+ for (let index = 0; index<services.length; index++) {
33
+ const service = services[index];
34
+ service.kindCandidates = [ ]
35
+ service.nameCandidates = [ ]
36
+
37
+ const { label, name, tags } = service.binding && service.binding.vcap || {};
38
+ if (!label) continue;
39
+
40
+ if (!vcapServices[label]) vcapServices[label] = [];
41
+ vcapServices[label].push({ credentials: { binding: { index } }, label, name, tags });
42
+ }
43
+
44
+ // Auto configure services based on VCAP_SERVICES
45
+ const cds = require('@sap/cds/lib');
46
+ const VCAP_SERVICES = process.env.VCAP_SERVICES;
47
+ try {
48
+ process.env.VCAP_SERVICES = JSON.stringify(vcapServices);
49
+ const env = cds.env.for('cds', '.');
50
+
51
+ // Extract "kind" from auto configured services
52
+ for (const kind of Object.keys(env.requires)) {
53
+ const requireService = env.requires[kind];
54
+ const superKind = requireService.kind;
55
+ const index = getIndex(requireService);
56
+ if (!Number.isInteger(index)) continue;
57
+
58
+ const superRequireService = env.requires[superKind];
59
+ if (superRequireService) {
60
+ const superIndex = getIndex(superRequireService);
61
+ if (index === superIndex) continue;
62
+ }
63
+
64
+ services[index].kindCandidates.push(kind);
65
+ }
66
+
67
+ // Check if one "kind" per service was found
68
+ for (const service of services) {
69
+ if (service.kindCandidates.length === 1) {
70
+ service.kind = service.kindCandidates[0];
71
+ }
72
+
73
+ delete service.kindCandidates;
74
+ }
75
+ } finally {
76
+ if (VCAP_SERVICES)
77
+ process.env.VCAP_SERVICES = VCAP_SERVICES;
78
+ else
79
+ delete process.env.VCAP_SERVICES;
80
+ }
81
+ }
82
+
83
+ function execAndExit(command, ...args) {
84
+ const { spawnSync } = require('child_process');
85
+ const result = spawnSync(command, args, { stdio: 'inherit', shell: false });
86
+ process.exit(result.status);
87
+ }
88
+
89
+ async function checkIsJavaProject(projectPath) {
90
+ const { constants, promises: fsp } = require('fs');
91
+ const path = require('path');
92
+ const pomXmlPath = path.join(projectPath, 'pom.xml');
93
+ try {
94
+ await fsp.access(pomXmlPath, constants.F_OK);
95
+ return true;
96
+ } catch (err) {
97
+ return false;
98
+ }
99
+ }
100
+
101
+
102
+ /**
103
+ *
104
+ * @param {string} msg
105
+ * @returns {never}
106
+ */
107
+ function throwError(msg = 'Internal Error') {
108
+ throw new Error(msg);
109
+ }
110
+
111
+
112
+ const PLATFORM_TYPES = {
113
+ 'cf': 'Cloud Foundry',
114
+ 'k8s': 'Kubernetes'
115
+ }
116
+
117
+ function platformText(binding) {
118
+ return PLATFORM_TYPES[binding.type] || throwError(`Unexpected binding type "${binding.type}"`);
119
+ }
120
+
121
+ function bindingText(binding, highlight = t => t) {
122
+ switch (binding.type) {
123
+ case 'cf':
124
+ return `service ${highlight(binding.instance)} key ${highlight(binding.key)}`
125
+ case 'k8s':
126
+ if (binding.name) {
127
+ return `service binding ${highlight(binding.name)}`
128
+ } else {
129
+ return `secret ${highlight(binding.secret)}`
130
+ }
131
+ default:
132
+ throwError();
133
+ }
134
+ }
135
+
136
+ module.exports = {
137
+ getRequiresNameFromKind,
138
+ determineKinds,
139
+ execAndExit,
140
+ checkIsJavaProject,
141
+ throwError,
142
+ platformText,
143
+ bindingText
144
+ }
@@ -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
 
@@ -20,11 +20,7 @@ const JOB_STATUS = {
20
20
  };
21
21
 
22
22
  async function checkFinished(jobId, statusUrl, params, finisher) {
23
- const requestOptions = Object.assign({
24
- method: 'GET'
25
- }, params.get('reqAuth'));
26
-
27
- const { status, error } = (await axios(statusUrl, requestOptions)).data;
23
+ const { status, error } = (await axios.get(statusUrl, params.get('reqAuth'))).data;
28
24
 
29
25
  if ((status === JOB_STATUS.FINISHED || status === JOB_STATUS.FAILED) && !finisher.finished) {
30
26
  finisher.finished = true;
@@ -170,16 +166,14 @@ module.exports = class Activate extends BaseCommand {
170
166
 
171
167
  activationUrl = Activate.getMtxApiUrl(params.get('appUrl'), COMMAND_URL_SUFFIX);
172
168
  const requestOptions = Object.assign({
173
- method: 'POST',
174
- headers: {},
175
- data: {
176
- extension: [...allFiles],
177
- undeployExtension: params.get('undeploy')
178
- }
169
+ headers: {}
179
170
  }, params.get('reqAuth'));
180
171
  requestOptions.headers['Content-Type'] = 'application/json';
181
172
 
182
- return axios(activationUrl, requestOptions);
173
+ return axios.post(activationUrl, {
174
+ extension: [...allFiles],
175
+ undeployExtension: params.get('undeploy')
176
+ }, requestOptions);
183
177
  })
184
178
  .then(response => {
185
179
  if (response === null) {
@@ -227,15 +221,13 @@ module.exports = class Activate extends BaseCommand {
227
221
 
228
222
  const customFileUrl = Activate.getMtxApiUrl(params.get('appUrl'), Activate.UPDATE_CUSTOM_FILE_URL_SUFFIX);
229
223
  const requestOptions = Object.assign({
230
- method: 'POST',
231
224
  headers: {},
232
- data: {
233
- content: customFilesSerialized
234
- }
235
225
  }, params.get('reqAuth'));
236
226
  requestOptions.headers['Content-Type'] = 'application/json';
237
227
 
238
- await axios(customFileUrl, requestOptions);
228
+ await axios.post(customFileUrl, {
229
+ content: customFilesSerialized
230
+ }, requestOptions);
239
231
 
240
232
  Logger.log('Custom file upload finished');
241
233
  }
@@ -58,7 +58,7 @@ async function retrieveTokenOrPasscodeUrl(params) {
58
58
 
59
59
  let response;
60
60
  try {
61
- response = await axios(tokenUrl, { method: 'GET' });
61
+ response = await axios.get(tokenUrl);
62
62
  } catch (error) {
63
63
  throw new Error(`Failed to get authentication token: ${error}.\nResponse body: ${util.inspect(response && response.data)}`);
64
64
  }
@@ -104,15 +104,12 @@ module.exports = class Extend extends BaseCommand {
104
104
  }
105
105
 
106
106
  static async runExtend(params) {
107
- const extendUrl = Extend.getMtxApiUrl(params.get('appUrl'), COMMAND_URL_SUFFIX);
108
- const requestOptions = Object.assign({
109
- method: 'GET',
110
- retry: 0
111
- }, params.get('reqAuth'));
107
+ const contentUrl = Extend.getMtxApiUrl(params.get('appUrl'), COMMAND_URL_SUFFIX);
108
+ const requestOptions = Object.assign({ retry: 0 }, params.get('reqAuth'));
112
109
 
113
110
  Logger.log('Retrieving files...');
114
111
 
115
- const [ response ] = (await axios(extendUrl, requestOptions)).data;
112
+ const [ response ] = (await axios.get(contentUrl, requestOptions)).data;
116
113
 
117
114
  Logger.log('\nWriting files to: ' + params.get('projectFolder'));
118
115
 
@@ -142,11 +139,9 @@ module.exports = class Extend extends BaseCommand {
142
139
  }
143
140
 
144
141
  if (params.get('wsdl')) {
145
-
146
142
  Logger.log('Writing custom files');
147
-
148
143
  const customFileUrl = Extend.getMtxApiUrl(params.get('appUrl'), Extend.CUSTOM_FILE_URL_SUFFIX);
149
- const customFiles = (await axios(customFileUrl, params)).data;
144
+ const customFiles = (await axios.get(customFileUrl, requestOptions)).data;
150
145
  await Extend.storeCustomFiles(params.get('projectFolder'), customFiles);
151
146
  }
152
147
  }
@@ -138,7 +138,7 @@ class SettingsManager {
138
138
  if (params.get('clearOtherTokenStorage')) {
139
139
  // Delete token from the other storage.
140
140
  params.delete('clearOtherTokenStorage');
141
- await this.deleteToken(params, other(params.get('tokenStorage')));
141
+ await this.deleteToken(params, true);
142
142
  }
143
143
 
144
144
  if (params.get('tokenStorage') === 'plain') {
@@ -303,15 +303,16 @@ class SettingsManager {
303
303
  }
304
304
  }
305
305
 
306
- static async deleteToken(params, fromStorage) {
306
+ static async deleteToken(params, fromOtherStorage = false) {
307
307
  const allParams = params.clone();
308
308
  await this.loadAndMergeSettings(allParams, true);
309
309
 
310
310
  const target = `URL ${allParams.get('appUrl')}, subdomain '${allParams.get('subdomain')}'`;
311
- Logger.debug(`Deleting authentication data for ${target}`);
312
-
311
+ let fromStorage = fromOtherStorage && other(params.get('tokenStorage'))
313
312
  const deleteBoth = !fromStorage && allParams.get('tokenStorage') && allParams.get('clearOtherTokenStorage');
314
313
 
314
+ Logger.debug(`Deleting authentication data${fromOtherStorage ? ' from other storage' : ''} for ${target}`);
315
+
315
316
  fromStorage = fromStorage || allParams.get('tokenStorage');
316
317
  if (!fromStorage) {
317
318
  Logger.log(`No authentication data found for ${target}`);
@@ -130,12 +130,13 @@ function _convertEDMXWithMultipleSchemas(edmx, multipleSchemas, namespace) {
130
130
  }
131
131
 
132
132
  async function edmx2csn(edmx, filename, context) {
133
- const result = edmx.match(/EntityType="([^.]+)/);
133
+ const result = edmx.match(/EntityType="([^"]+)/);
134
134
  const multipleSchemas = /(\s*<\/Schema>\s*<Schema [^>]*>)\s*/m;
135
135
  if (!result) {
136
136
  throw new Error(messages.MISSING_ENTITIES);
137
137
  }
138
- const namespace = result[1];
138
+ const index = result[1].lastIndexOf(".");
139
+ const namespace = (index > 0 ) ? result[1].substring(0, index) : result[1];
139
140
 
140
141
  if (multipleSchemas.test(edmx)) {
141
142
  edmx = _convertEDMXWithMultipleSchemas(edmx, multipleSchemas, namespace);
@@ -0,0 +1,38 @@
1
+ # OData V2 or V4 EDM Datatype mapping to CAP CDS
2
+
3
+ oData V2/V4 | CAP CDS | Comments (if any) |
4
+ ------------|----------| ----------|
5
+ Edm.Guid |cds.UUID | |
6
+ Edm.Boolean |cds.Boolean| |
7
+ Edm.Int16 |cds.Integer| |
8
+ Edm.Int32 |cds.Integer| |
9
+ Edm.Int64 | cds.Integer64| |
10
+ Edm.Decimal | cds.DecimalFloat| |
11
+ Edm.Decimal Precision > 0 | cds.Decimal| |
12
+ Edm.Single | cds.Double @odata.type: “Edm.Single”| |
13
+ Edm.Double | cds.Double| |
14
+ Edm.Double Precision > 0 |cds.Double| |
15
+ Edm.String | cds.LargeString| |
16
+ Edm.String MaxLength > 0 |cds.String| |
17
+ Edm.Binary MaxLength > 0 | cds.Binary | |
18
+ Edm.Binary | cds.LargeBinary | |
19
+ Edm.Byte |cds.Integer| |
20
+ Edm.Byte MaxLength > 0 |cds.Binary| |
21
+ Edm.SByte |cds.Integer| |
22
+ Edm.Stream | cds.LargeBinary @odata.type: “Edm.Stream” | |
23
+ Edm.DateTimeOffset Precision : Microsecond | cds.Timestamp, @odata.type: “Edm.DateTimeOffset”, @odata.precision:<> | The value of precision from odata will be mapped to @odata.precision |
24
+ Edm.DateTimeOffset Precision : Second | cds.DateTime, @odata.type: “Edm.DateTimeOffset”, @odata.precision:0| |
25
+ | |
26
+ **Below attributes are applicable to V2 only** |
27
+ | |
28
+ Edm.DateTime Precision : Microsecond | cds.Timestamp, @odata.type: “Edm.DateTime”, @odata.precision:<> | The value of precision from odata will be mapped to @odata.precision | |
29
+ Edm.DateTime Precision : Second | cds.DateTime, @odata.type: “Edm.DateTime”, @odata.precision:0| |
30
+ Edm.Time |cds.Time| |
31
+ Edm.DateTime sap:display-format="Date" |cds.Date| |
32
+ Edm.DateTimeOffset sap:display-format="Date" |cds.Date||
33
+ **Below attributes are applicable to V4 only** |
34
+ | |
35
+ Edm.TimeOfDay |cds.Time||
36
+ Edm.Date |cds.Date||
37
+
38
+
@@ -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