@sap/cds-dk 9.6.1 → 9.7.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 (82) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/bin/add.js +5 -2
  3. package/bin/cds.js +10 -25
  4. package/bin/data-products/import.js +3 -0
  5. package/bin/debug.js +76 -16
  6. package/bin/env.js +2 -1
  7. package/bin/export.js +18 -13
  8. package/bin/help/export.md +2 -1
  9. package/bin/import.js +21 -18
  10. package/bin/init.js +8 -1
  11. package/bin/minify.js +11 -12
  12. package/bin/mock.js +17 -9
  13. package/bin/repl.js +2 -2
  14. package/bin/up.js +34 -4
  15. package/bin/version.js +202 -131
  16. package/lib/build/buildTaskEngine.js +4 -0
  17. package/lib/build/buildTaskFactory.js +5 -35
  18. package/lib/build/index.js +10 -10
  19. package/lib/build/plugin.js +6 -3
  20. package/lib/build/plugins/helm/files/subcharts/content-deployment/Chart.yaml +3 -1
  21. package/lib/build/plugins/helm/files/subcharts/content-deployment/templates/_helpers.tpl +1 -1
  22. package/lib/build/plugins/helm/files/subcharts/content-deployment/templates/job.yaml +8 -0
  23. package/lib/build/plugins/helm/files/subcharts/content-deployment/values.schema.json +18 -0
  24. package/lib/build/plugins/helm/files/subcharts/content-deployment/values.yaml +11 -1
  25. package/lib/build/plugins.js +8 -16
  26. package/lib/build/provider/hana/index.js +16 -19
  27. package/lib/build/provider/internalBuildPlugin.js +5 -4
  28. package/lib/build/provider/internalBuildTaskProvider.js +26 -88
  29. package/lib/build/provider/mtx/index.js +0 -8
  30. package/lib/build/provider/mtx-sidecar/index.js +88 -80
  31. package/lib/build/provider/nodejs/index.js +3 -8
  32. package/lib/build/util.js +52 -87
  33. package/lib/deploy/to-hana/hana.js +117 -109
  34. package/lib/env/schemas/cds-rc.js +0 -4
  35. package/lib/import/odata/v4parser.js +80 -56
  36. package/lib/init/constants.js +5 -2
  37. package/lib/init/index.js +42 -40
  38. package/lib/init/merge.js +7 -2
  39. package/lib/init/projectReader.js +2 -8
  40. package/lib/init/template/ams/index.js +1 -4
  41. package/lib/init/template/attachments/files/package.json.hbs +1 -1
  42. package/lib/init/template/data/index.js +1 -1
  43. package/lib/init/template/extension/index.js +4 -4
  44. package/lib/init/template/hana/files/package.json.hbs +3 -3
  45. package/lib/init/template/handler/index.js +1 -1
  46. package/lib/init/template/helm/index.js +3 -3
  47. package/lib/init/template/helm-unified-runtime/index.js +5 -1
  48. package/lib/init/template/html5-repo/files/mta.yaml.hbs +1 -1
  49. package/lib/init/template/ias/files/mta.yaml.hbs +3 -3
  50. package/lib/init/template/initial/files/.vscode/tasks.json +10 -0
  51. package/lib/init/template/{nodejs/files/README.md → initial/files/readme.md} +2 -3
  52. package/lib/init/template/initial/index.js +15 -0
  53. package/lib/init/template/java/index.js +4 -11
  54. package/lib/init/template/kyma/index.js +21 -20
  55. package/lib/init/template/lint/index.js +3 -1
  56. package/lib/init/template/mta/index.js +0 -5
  57. package/lib/init/template/multitenancy/files/package.sidecar.json.hbs +1 -2
  58. package/lib/init/template/multitenancy/files/values.yaml.hbs +30 -0
  59. package/lib/init/template/nodejs/files/.vscode/tasks.json +1 -14
  60. package/lib/init/template/nodejs/files/package.json.hbs +2 -4
  61. package/lib/init/template/nodejs/index.js +8 -12
  62. package/lib/init/template/sample/index.js +5 -6
  63. package/lib/init/template/sqlite/files/package.json.hbs +7 -1
  64. package/lib/init/template/test/index.js +1 -1
  65. package/lib/init/template/tiny-sample/files/nodejs/db/data/CatalogService.Books.csv +6 -0
  66. package/lib/init/template/tiny-sample/files/nodejs/srv/cat-service.cds +5 -0
  67. package/lib/init/template/tiny-sample/index.js +2 -2
  68. package/lib/init/template/workzone-standard/files/mta.yaml.hbs +6 -0
  69. package/lib/init/template/workzone-standard/index.js +13 -3
  70. package/lib/init/validate.js +11 -6
  71. package/lib/util/checksum.js +2 -2
  72. package/lib/util/fs.js +109 -53
  73. package/lib/util/glob.js +2 -1
  74. package/lib/util/term.js +1 -7
  75. package/lib/util/write.js +2 -4
  76. package/lib/watch/watched.js +1 -1
  77. package/npm-shrinkwrap.json +226 -259
  78. package/package.json +2 -2
  79. package/lib/init/template/nodejs/files/.vscode/settings.json +0 -11
  80. package/lib/init/template/tiny-sample/files/nodejs/db/data/my.bookshop-Books.csv +0 -3
  81. package/lib/init/template/tiny-sample/files/nodejs/db/schema.cds +0 -7
  82. package/lib/init/template/tiny-sample/files/nodejs/srv/cat-service.cds.hbs +0 -5
@@ -24,14 +24,15 @@ const allowList = [
24
24
  '--auto-undeploy',
25
25
  '--dry',
26
26
  '--for',
27
+ '--no-build',
27
28
  '--on',
29
+ '--out',
30
+ '--resolve-bindings',
28
31
  '--store-credentials',
29
32
  '--to',
30
33
  '--tunnel-address',
31
34
  '--vcap-file',
32
- '--with-mocks',
33
- '--no-build',
34
- '--resolve-bindings'
35
+ '--with-mocks'
35
36
  ]
36
37
 
37
38
  module.exports = new class HanaDeployer {
@@ -51,6 +52,11 @@ module.exports = new class HanaDeployer {
51
52
  }
52
53
 
53
54
  async deploy(options = {}) {
55
+
56
+ if (options.dry && options.out) { // dry run equals cds compile to hana
57
+ return await this._runCompileToHana(options);
58
+ }
59
+
54
60
  let unknownOptions = Object.keys(options)
55
61
  .map(key => `--${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`)
56
62
  .filter(key => allOptions.includes(key) && !allowList.includes(key));
@@ -59,7 +65,7 @@ module.exports = new class HanaDeployer {
59
65
  throw `One or more options are not supported by cds deploy to hana: ${unknownOptions.join(', ')}`;
60
66
  }
61
67
 
62
- let { model, serviceName: pServiceName, tunnelAddress, vcapFile, undeployWhitelist, hdiOptions = {}, dry } = options;
68
+ let { model, serviceName: pServiceName, tunnelAddress, vcapFile, undeployWhitelist, hdiOptions = {} } = options;
63
69
 
64
70
  options.for ??= 'hybrid';
65
71
 
@@ -69,10 +75,6 @@ module.exports = new class HanaDeployer {
69
75
  Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
70
76
  }
71
77
 
72
- if (dry) {
73
- console.log('--- running in dry mode ---');
74
- }
75
-
76
78
  if (vcapFile) {
77
79
  console.log(`Using VCAP_SERVICES from file ${vcapFile}.`);
78
80
  }
@@ -97,36 +99,34 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
97
99
  buildResult = await this._build();
98
100
  }
99
101
 
102
+ if (options.out) {
103
+ await this._runCompileToHana(options);
104
+ }
105
+
106
+ if (options.dry) {
107
+ return;
108
+ }
109
+
100
110
  if (process.env.TARGET_CONTAINER) {
101
111
  vcapEnv.TARGET_CONTAINER = process.env.TARGET_CONTAINER;
102
112
  }
103
113
 
104
- if (dry) {
105
- if (!buildResult) return
106
- const sortedResult = Array.from(buildResult.hana).sort();
107
- for (const file of sortedResult) {
108
- const srcPath = path.join(buildResult.dest, file);
109
- console.log(`-- ${path.relative(cds.root, srcPath)}`);
110
- console.log(await read(srcPath));
111
- console.log();
112
- }
113
- } else {
114
- // the primary database is always built and deployed
115
- // const dest = buildResult.dest
116
- const dest = path.join(cds.root, cds.env.build.target, cds.env.folders.db);
114
+ // the primary database is always built and deployed
115
+ // const dest = buildResult.dest
116
+ const dest = path.join(cds.root, cds.env.build.target, cds.env.folders.db);
117
117
 
118
- if (undeployWhitelist) {
119
- console.log('Writing undeploy.json');
120
- await write(undeployWhitelist).to(dest, 'undeploy.json')
121
- }
118
+ if (undeployWhitelist) {
119
+ console.log('Writing undeploy.json');
120
+ await write(undeployWhitelist).to(dest, 'undeploy.json')
121
+ }
122
122
 
123
- const hasVCAPEnv = Object.keys(vcapEnv).length > 0;
123
+ const hasVCAPEnv = Object.keys(vcapEnv).length > 0;
124
124
 
125
- let bindOptions
126
- if (cds.cli.options.on === 'k8s') {
127
- const cp = require('node:child_process')
128
- const appName = pServiceName ?? path.basename(cds.root)
129
- const instance =
125
+ let bindOptions
126
+ if (cds.cli.options.on === 'k8s') {
127
+ const cp = require('node:child_process')
128
+ const appName = pServiceName ?? path.basename(cds.root)
129
+ const instance =
130
130
  `apiVersion: services.cloud.sap.com/v1
131
131
  kind: ServiceInstance
132
132
  metadata:
@@ -136,7 +136,7 @@ spec:
136
136
  servicePlanName: hdi-shared
137
137
  `
138
138
 
139
- const binding =
139
+ const binding =
140
140
  `apiVersion: services.cloud.sap.com/v1
141
141
  kind: ServiceBinding
142
142
  metadata:
@@ -144,95 +144,93 @@ metadata:
144
144
  spec:
145
145
  serviceInstanceName: ${appName}-db
146
146
  secretName: ${appName}-db-secret
147
-
148
147
  `
149
148
 
150
- try {
151
- cp.execSync('kubectl apply -f -', { input: instance })
152
- cp.execSync('kubectl apply -f -', { input: binding })
153
- cp.execSync(`kubectl wait --for=condition=Ready servicebinding/${appName}-db-binding --timeout=120s`)
154
- cp.execSync(`kubectl wait --for=condition=Ready serviceinstance/${appName}-db --timeout=120s`)
155
- } catch (error) {
156
- const descr = cp.execSync(`kubectl describe serviceinstance/${appName}-db`).toString()
157
- const message = descr.match(/Message:\s+(.*)/)?.[1] || 'None'
158
- throw error.message + `\nInstance Message: ${message}`
159
- }
149
+ try {
150
+ cp.execSync('kubectl apply -f -', { input: instance })
151
+ cp.execSync('kubectl apply -f -', { input: binding })
152
+ cp.execSync(`kubectl wait --for=condition=Ready servicebinding/${appName}-db-binding --timeout=180s`)
153
+ cp.execSync(`kubectl wait --for=condition=Ready serviceinstance/${appName}-db --timeout=180s`)
154
+ } catch (error) {
155
+ const descr = cp.execSync(`kubectl describe serviceinstance/${appName}-db`).toString()
156
+ const message = descr.match(/Message:\s+(.*)/)?.[1] || 'None'
157
+ throw error.message + `\nInstance Message: ${message}`
158
+ }
160
159
 
161
- const { data: encoded } = JSON.parse(cp.execSync(`kubectl get secrets/${appName}-db-secret -o json`).toString())
162
- const credentials = {}
163
- for (const [key, value] of Object.entries(encoded)) {
164
- credentials[key] = Buffer.from(value, 'base64').toString('utf-8');
165
- }
166
- vcapEnv.VCAP_SERVICES ??= {
167
- hana: [{
168
- tags: ['hana'],
169
- credentials
170
- }]
171
- }
172
- bindOptions = {
173
- ...options,
174
- to: `${appName}-db-binding`,
175
- for: options.for,
176
- kind: BUILD_TASK_HANA
177
- }
178
- } else {
179
- let serviceName = pServiceName;
180
- let serviceKeyName;
181
- const { instance, key } = cds.requires.db?.binding ?? {};
182
- if (!serviceName && instance) {
183
- serviceName = instance;
184
- serviceKeyName = key;
185
- }
160
+ const { data: encoded } = JSON.parse(cp.execSync(`kubectl get secrets/${appName}-db-secret -o json`).toString())
161
+ const credentials = {}
162
+ for (const [key, value] of Object.entries(encoded)) {
163
+ credentials[key] = Buffer.from(value, 'base64').toString('utf-8');
164
+ }
165
+ vcapEnv.VCAP_SERVICES ??= {
166
+ hana: [{
167
+ tags: ['hana'],
168
+ credentials
169
+ }]
170
+ }
171
+ bindOptions = {
172
+ ...options,
173
+ to: `${appName}-db-binding`,
174
+ for: options.for,
175
+ kind: BUILD_TASK_HANA
176
+ }
177
+ } else {
178
+ let serviceName = pServiceName;
179
+ let serviceKeyName;
180
+ const { instance, key } = cds.requires.db?.binding ?? {};
181
+ if (!serviceName && instance) {
182
+ serviceName = instance;
183
+ serviceKeyName = key;
184
+ }
186
185
 
187
- if (!serviceName && hasVCAPEnv) {
188
- await mkdirp(dest);
189
- } else {
190
- const { cfServiceInstanceName, cfServiceInstanceKeyName, serviceKey } = await this._getOrCreateCFService(dest, serviceName, serviceKeyName, tunnelAddress);
191
- serviceName ??= cfServiceInstanceName;
192
- serviceKeyName ??= cfServiceInstanceKeyName
193
- if (!vcapEnv.VCAP_SERVICES || serviceName) {
194
- const base = process.env.VCAP_SERVICES ? JSON.parse(process.env.VCAP_SERVICES) : (vcapEnv.VCAP_SERVICES ?? {});
195
- const hanas = Array.isArray(base.hana) ? base.hana : [];
196
- let hanaTagged = hanas.filter(s => s?.name === cfServiceInstanceName || (Array.isArray(s?.tags) && s.tags.includes('hana')));
197
- if (hanaTagged.length === 0) {
198
- const entry = { name: cfServiceInstanceName, label: 'hana', tags: ['hana'], credentials: {} };
199
- hanas.push(entry);
200
- hanaTagged = [entry];
201
- }
202
- for (const entry of hanaTagged) {
203
- entry.name ??= cfServiceInstanceName;
204
- entry.tags = Array.from(new Set([...entry.tags || [], 'hana']));
205
- entry.credentials = { ...(entry.credentials || {}), ...serviceKey };
206
- }
207
- base.hana = hanas;
208
- vcapEnv.VCAP_SERVICES = base;
209
- process.env.VCAP_SERVICES = JSON.stringify(base);
186
+ if (!serviceName && hasVCAPEnv) {
187
+ await mkdirp(dest);
188
+ } else {
189
+ const { cfServiceInstanceName, cfServiceInstanceKeyName, serviceKey } = await this._getOrCreateCFService(dest, serviceName, serviceKeyName, tunnelAddress);
190
+ serviceName ??= cfServiceInstanceName;
191
+ serviceKeyName ??= cfServiceInstanceKeyName
192
+ if (!vcapEnv.VCAP_SERVICES || serviceName) {
193
+ const base = process.env.VCAP_SERVICES ? JSON.parse(process.env.VCAP_SERVICES) : (vcapEnv.VCAP_SERVICES ?? {});
194
+ const hanas = Array.isArray(base.hana) ? base.hana : [];
195
+ let hanaTagged = hanas.filter(s => s?.name === cfServiceInstanceName || (Array.isArray(s?.tags) && s.tags.includes('hana')));
196
+ if (hanaTagged.length === 0) {
197
+ const entry = { name: cfServiceInstanceName, label: 'hana', tags: ['hana'], credentials: {} };
198
+ hanas.push(entry);
199
+ hanaTagged = [entry];
210
200
  }
201
+ for (const entry of hanaTagged) {
202
+ entry.name ??= cfServiceInstanceName;
203
+ entry.tags = Array.from(new Set([...entry.tags || [], 'hana']));
204
+ entry.credentials = { ...(entry.credentials || {}), ...serviceKey };
205
+ }
206
+ base.hana = hanas;
207
+ vcapEnv.VCAP_SERVICES = base;
208
+ process.env.VCAP_SERVICES = JSON.stringify(base);
209
+ }
211
210
 
212
- if (!hasVCAPEnv) {
213
- bindOptions = {
214
- ...options,
215
- to: `${serviceName}:${serviceKeyName}`,
216
- for: options.for,
217
- kind: BUILD_TASK_HANA
218
- }
211
+ if (!hasVCAPEnv) {
212
+ bindOptions = {
213
+ ...options,
214
+ to: `${serviceName}:${serviceKeyName}`,
215
+ for: options.for,
216
+ kind: BUILD_TASK_HANA
219
217
  }
220
218
  }
221
219
  }
220
+ }
222
221
 
223
- console.log(`${bold('starting deployment to SAP HANA ...')}`);
224
- DEBUG?.({ dest, vcapEnv, hdiOptions })
225
- await hdi.deploy(dest, vcapEnv, hdiOptions);
222
+ console.log(`${bold('starting deployment to SAP HANA ...')}`);
223
+ DEBUG?.({ dest, vcapEnv, hdiOptions })
224
+ await hdi.deploy(dest, vcapEnv, hdiOptions);
226
225
 
227
- if (!hasVCAPEnv) {
228
- const services = await bind(bindOptions)
229
- await storeBindings(services, bindOptions)
230
- }
226
+ if (!hasVCAPEnv) {
227
+ const services = await bind(bindOptions)
228
+ await storeBindings(services, bindOptions)
229
+ }
231
230
 
232
- const { env4 } = require('../../init/projectReader')
233
- if (env4('production').requires?.db?.kind !== 'hana') {
234
- console.log(`use ${info('cds add hana')} to configure the project for SAP HANA.\n`);
235
- }
231
+ const { env4 } = require('../../init/projectReader')
232
+ if (env4('production').requires?.db?.kind !== 'hana') {
233
+ console.log(`use ${info('cds add hana')} to configure the project for SAP HANA.\n`);
236
234
  }
237
235
 
238
236
  console.log(`successfully finished deployment`);
@@ -245,6 +243,16 @@ spec:
245
243
  return buildResult;
246
244
  }
247
245
 
246
+ async _runCompileToHana(options) {
247
+ const csn = await cds.load('*');
248
+ const files = cds.compile.to.hana(csn, options);
249
+ const outDir = path.resolve(cds.root, options.out);
250
+ for (const [content, { file }] of files) {
251
+ const filePath = path.join(outDir, file);
252
+ await cds.utils.write(content).to(filePath);
253
+ }
254
+ }
255
+
248
256
  async _getOrCreateCFService(currentModelFolder, serviceName, serviceKeyName, tunnelAddress) {
249
257
 
250
258
  // get from param
@@ -285,8 +285,6 @@ module.exports = {
285
285
  "mtx-sidecar",
286
286
  "mtx-extension",
287
287
  "mtx",
288
- "node-cf",
289
- "java-cf",
290
288
  "helm"
291
289
  ]),
292
290
  enumDescriptions: [
@@ -296,8 +294,6 @@ module.exports = {
296
294
  "MTX sidecar build plugin",
297
295
  "MTX extension build plugin",
298
296
  "MTX build plugin",
299
- "Node.js Cloud Foundry build plugin",
300
- "Java Cloud Foundry build plugin",
301
297
  "Helm chart build plugin"
302
298
  ]
303
299
  },
@@ -122,7 +122,7 @@ function _getIndexAndNamespaceOfSchema(fqName, schemaName, parserContext) {
122
122
  }
123
123
  // the schema namespace is not resolvable, throw error
124
124
  else {
125
- throw new Error(messages.UNRESOLVED_SCHEMA_NAMESPACE + fqName );
125
+ throw new Error(messages.UNRESOLVED_SCHEMA_NAMESPACE + fqName);
126
126
  }
127
127
  }
128
128
 
@@ -290,7 +290,7 @@ function _replaceSpecialCharacters(text) {
290
290
  function _resolveCoreTerm(annotationTerm, annotationTermObject, csnDefsTargetObject) {
291
291
  if (annotationTerm === "@Core.Description" || annotationTerm === "@Core.LongDescription") {
292
292
  // we don't capture `null` value for doc
293
- if (annotationTermObject === null) return;
293
+ if (annotationTermObject === null) return;
294
294
 
295
295
  annotationTermObject = _replaceSpecialCharacters(annotationTermObject);
296
296
 
@@ -474,9 +474,9 @@ function _resolveAnnotationTerm(schemaDataAnnotation, parserContext) {
474
474
  let index = schemaDataAnnotation.indexOf('"@');
475
475
  while (index != -1) {
476
476
  let i = schemaDataAnnotation.indexOf('"', index + 1);
477
- let annotationTerm = schemaDataAnnotation.substring(index+1, i);
477
+ let annotationTerm = schemaDataAnnotation.substring(index + 1, i);
478
478
  _validateAnnotationNamespace(annotationTerm, parserContext.vocabNamespaceToAlias);
479
- index = schemaDataAnnotation.indexOf('"@', i+1);
479
+ index = schemaDataAnnotation.indexOf('"@', i + 1);
480
480
  }
481
481
 
482
482
  return JSON.parse(schemaDataAnnotation);
@@ -688,10 +688,23 @@ function _parseAssociationProperty(foreignReference, associationNode, parserCont
688
688
  // "generation" of keys, that do not exist in the external service.
689
689
  if (associationNode.$Partner && associationProperty.type === 'cds.Composition') {
690
690
  associationProperty.on = [
691
- {ref: [foreignReference, associationNode.$Partner]},
691
+ { ref: [foreignReference, associationNode.$Partner] },
692
692
  '=',
693
- {ref: ['$self']}
693
+ { ref: ['$self'] }
694
694
  ]
695
+ } else if (associationNode.$ReferentialConstraint) {
696
+ associationProperty.on = []
697
+ for (const constraint in associationNode.$ReferentialConstraint) {
698
+ if (associationProperty.on.length) {
699
+ associationProperty.on.push('and')
700
+ }
701
+ associationProperty.on.push(
702
+ { ref: [foreignReference, associationNode.$ReferentialConstraint[constraint]] },
703
+ '=',
704
+ { ref: [constraint] }
705
+ )
706
+ }
707
+
695
708
  } else if (common.checkForEmptyKeys(associationProperty, "V4")) {
696
709
  associationProperty.keys = [];
697
710
  }
@@ -738,52 +751,63 @@ function _parsePropertyContent(csnObj, elementObj, parserContext) {
738
751
  const isPrimaryKey = true;
739
752
  let dataType, isPrimitiveType, keySet = [];
740
753
 
741
- Object.keys(elementObj).forEach(objKey => {
742
- const contentKind = elementObj[objKey].$Kind;
743
- contentObj = elementObj[objKey];
744
-
745
- if ((objKey.indexOf('@') === -1) && (objKey.indexOf('.') === -1)) {
746
- if (contentObj instanceof Object) {
747
- if (contentObj.$Type) {
748
- dataType = contentObj.$Type;
749
- isPrimitiveType = dataType.startsWith('Edm') ? true : false;
750
- }
751
- if (objKey === '$Key') {
752
- keySet.push(...elementObj.$Key);
753
- }
754
- // primitive datatypes
755
- else if (isPrimitiveType) {
756
- if (edmPrimitiveTypes.includes(dataType)) {
757
- csnObj.elements[objKey] = _generatePrimitiveDataTypeCsn(contentObj,
758
- (keySet.includes(objKey) ? isPrimaryKey : !isPrimaryKey),
759
- parserContext
760
- );
761
- } else {
762
- throw new Error(messages.INVALID_DATATYPE + `'${dataType}' in element: '${objKey}'`)
754
+ Object.keys(elementObj)
755
+ .sort((ele1, ele2) => {
756
+ if (elementObj[ele1]?.$Kind === elementObj[ele2]?.$Kind) {
757
+ return 0
758
+ } else if (elementObj[ele1]?.$Kind === 'NavigationProperty') {
759
+ return 1
760
+ } else if (elementObj[ele2]?.$Kind === 'NavigationProperty') {
761
+ return -1
762
+ }
763
+ return 0
764
+ })
765
+ .forEach(objKey => {
766
+ const contentKind = elementObj[objKey].$Kind;
767
+ contentObj = elementObj[objKey];
768
+
769
+ if ((objKey.indexOf('@') === -1) && (objKey.indexOf('.') === -1)) {
770
+ if (contentObj instanceof Object) {
771
+ if (contentObj.$Type) {
772
+ dataType = contentObj.$Type;
773
+ isPrimitiveType = dataType.startsWith('Edm') ? true : false;
763
774
  }
764
- }
765
- // structured datatypes
766
- else if (!isPrimitiveType && !(contentKind === 'NavigationProperty')) {
767
- const dataKind = _checkIsStructureTypeAndReturnDataKind(dataType, contentObj, 'property', parserContext);
768
-
769
- if (['EnumType', 'TypeDefinition'].includes(dataKind)) {
770
- csnObj.elements[objKey] = _generateStructuredDataTypeCsn(dataType, contentObj,
771
- (keySet.includes(dataType) ? isPrimaryKey : !isPrimaryKey),
772
- dataKind, 'property', parserContext
773
- );
775
+ if (objKey === '$Key') {
776
+ keySet.push(...elementObj.$Key);
774
777
  }
775
- else {
776
- csnObj.elements[objKey] = _generateStructuredDataTypeCsn(dataType, contentObj,
777
- !isPrimaryKey, dataKind, 'property', parserContext
778
- );
778
+ // primitive datatypes
779
+ else if (isPrimitiveType) {
780
+ if (edmPrimitiveTypes.includes(dataType)) {
781
+ csnObj.elements[objKey] = _generatePrimitiveDataTypeCsn(contentObj,
782
+ (keySet.includes(objKey) ? isPrimaryKey : !isPrimaryKey),
783
+ parserContext
784
+ );
785
+ } else {
786
+ throw new Error(messages.INVALID_DATATYPE + `'${dataType}' in element: '${objKey}'`)
787
+ }
788
+ }
789
+ // structured datatypes
790
+ else if (!isPrimitiveType && !(contentKind === 'NavigationProperty')) {
791
+ const dataKind = _checkIsStructureTypeAndReturnDataKind(dataType, contentObj, 'property', parserContext);
792
+
793
+ if (['EnumType', 'TypeDefinition'].includes(dataKind)) {
794
+ csnObj.elements[objKey] = _generateStructuredDataTypeCsn(dataType, contentObj,
795
+ (keySet.includes(dataType) ? isPrimaryKey : !isPrimaryKey),
796
+ dataKind, 'property', parserContext
797
+ );
798
+ }
799
+ else {
800
+ csnObj.elements[objKey] = _generateStructuredDataTypeCsn(dataType, contentObj,
801
+ !isPrimaryKey, dataKind, 'property', parserContext
802
+ );
803
+ }
804
+ }
805
+ else if (contentKind === 'NavigationProperty') {
806
+ csnObj.elements[objKey] = _parseAssociationProperty(objKey, elementObj[objKey], parserContext);
779
807
  }
780
- }
781
- else if (contentKind === 'NavigationProperty') {
782
- csnObj.elements[objKey] = _parseAssociationProperty(objKey, elementObj[objKey], parserContext);
783
808
  }
784
809
  }
785
- }
786
- });
810
+ });
787
811
  }
788
812
 
789
813
  function _addBlobElement() {
@@ -960,7 +984,7 @@ function _parseBoundActionFunction(schemaData, csnObj, kind, parserContext) {
960
984
  const boundObject = (kind === 'action') ? parserContext.entityToBoundActions : parserContext.entityToBoundFunctions;
961
985
 
962
986
  Object.keys(boundObject).forEach(fqEntityName => {
963
- const [schemaName, ] = _extractSchemaNameAndElementName(fqEntityName);
987
+ const [schemaName,] = _extractSchemaNameAndElementName(fqEntityName);
964
988
 
965
989
  if (schemaName === schemaData.$SchemaNamespace) {
966
990
  const actionFunctionObjectList = boundObject[fqEntityName];
@@ -982,7 +1006,7 @@ function _parseUnboundActionFunction(schemaData, kind, parserContext) {
982
1006
  const csnObj = {};
983
1007
 
984
1008
  Object.keys(unboundObject).forEach(fqName => {
985
- const [schemaName, ] = _extractSchemaNameAndElementName(fqName);
1009
+ const [schemaName,] = _extractSchemaNameAndElementName(fqName);
986
1010
 
987
1011
  if (schemaName === schemaData.$SchemaNamespace) {
988
1012
  const actionFunctionObject = unboundObject[fqName];
@@ -1039,7 +1063,7 @@ function _parserCSNForComplexType(schemaData, parserContext) {
1039
1063
 
1040
1064
  function _parseCSNForEnumType(schemaData, parserContext) {
1041
1065
  const nonMemberList = ["$Kind", "$Type", "$UnderlyingType", "$IsFlags"];
1042
- const possibleCdsTypes = ['cds.Integer', 'cds.Integer64'];
1066
+ const possibleCdsTypes = ['cds.Integer', 'cds.Integer64'];
1043
1067
  const enumCsn = {};
1044
1068
  const isKey = true;
1045
1069
 
@@ -1058,7 +1082,7 @@ function _parseCSNForEnumType(schemaData, parserContext) {
1058
1082
  let underlyingType = enumObj.$UnderlyingType;
1059
1083
  enumObj.$Type = underlyingType;
1060
1084
 
1061
- let cdsType = underlyingType ? (_generatePrimitiveDataTypeCsn(enumObj, !isKey, parserContext)) : { type : 'cds.Integer' };
1085
+ let cdsType = underlyingType ? (_generatePrimitiveDataTypeCsn(enumObj, !isKey, parserContext)) : { type: 'cds.Integer' };
1062
1086
  enumCsn[fqName].type = cdsType.type;
1063
1087
 
1064
1088
  if (cdsType['@odata.Type']) {
@@ -1127,7 +1151,7 @@ function _resolveEntityContainerAnnotationTarget(targetPath, csnDefs, hasPropert
1127
1151
  }
1128
1152
  // direct path to service object
1129
1153
  else {
1130
- return csnDefs[parserContext.primarySchema];
1154
+ return csnDefs[parserContext.primarySchema];
1131
1155
  }
1132
1156
  }
1133
1157
 
@@ -1396,7 +1420,7 @@ function _populateUnboundActionFunction(isAction, fqName, schemaDataList, parser
1396
1420
  actionFunctionObject['$Parameter'] = [];
1397
1421
  parserContext.unboundFunctions[fqName] = actionFunctionObject;
1398
1422
  parserContext.unboundFunctions[fqName]["@cds.validate"] = false;
1399
- if(parserContext.unboundFunctions[fqName].$ReturnType){
1423
+ if (parserContext.unboundFunctions[fqName].$ReturnType) {
1400
1424
  parserContext.unboundFunctions[fqName].$ReturnType.$Type = "Edm.String";
1401
1425
  }
1402
1426
  }
@@ -1611,8 +1635,7 @@ function _preProcess(edmxAsJson, parserContext, importerContext) {
1611
1635
  parserContext.entityToEntitySetMap._.size === 0 &&
1612
1636
  parserContext.entityToSingletonMap._.size === 0 &&
1613
1637
  parserContext.actionToActionImportMap._.size === 0 &&
1614
- parserContext.functionToFunctionImportMap._.size === 0)
1615
- {
1638
+ parserContext.functionToFunctionImportMap._.size === 0) {
1616
1639
  parserContext.mockServerUc = false;
1617
1640
  }
1618
1641
 
@@ -1686,7 +1709,8 @@ function _generateCSN(edmxAsJson, ignorePersistenceSkip, parserContext, importer
1686
1709
  compTarget.elements[e].type === 'cds.Association' &&
1687
1710
  compTarget.elements[e].target === eName
1688
1711
  )
1689
- if (compTarget.elements[backLink]?.keys) {
1712
+ const entityKeys = Object.keys(entity.elements).filter(ele => entity.elements[ele].key)
1713
+ if (compTarget.elements[backLink]?.keys && !entityKeys.some(key => compTarget.elements[backLink + '_' + key])) {
1690
1714
  delete compTarget.elements[backLink].keys
1691
1715
  }
1692
1716
  }
@@ -5,7 +5,7 @@ module.exports = {
5
5
  COMMAND_ADD: 'add',
6
6
 
7
7
  JAVA_LTS_VERSIONS: [17, 21],
8
- MAVEN_ARCHETYPE_VERSION: '4.6.1',
8
+ MAVEN_ARCHETYPE_VERSION: '4.6.2',
9
9
 
10
10
  OPTIONS: Object.freeze({
11
11
  COMPLETION: 'completion',
@@ -82,7 +82,10 @@ module.exports = {
82
82
  CAPIRE: 'https://cap.cloud.sap',
83
83
  MAVEN_ARCHETYPE_HELP: `https://cap.cloud.sap/docs/java/developing-applications/building#the-maven-archetype`,
84
84
  MAVEN_INSTALL_HELP: `https://maven.apache.org/install.html`,
85
- CF_CLI: 'https://github.com/cloudfoundry/cli'
85
+ CF_INSTALL: 'https://docs.cloudfoundry.org/cf-cli/install-go-cli.html',
86
+ HELM_INSTALL: 'https://helm.sh/docs/intro/install/',
87
+ KUBECTL_INSTALL: 'https://kubernetes.io/docs/tasks/tools/',
88
+ DOCKER_INSTALL: 'https://docs.docker.com/get-docker/'
86
89
  }),
87
90
 
88
91
  PROJECT_FILES: ['package.json', 'pom.xml', '.cdsrc.json']