@sap/cds-dk 8.3.0 → 8.4.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 (54) hide show
  1. package/CHANGELOG.md +59 -13
  2. package/bin/build.js +1 -1
  3. package/bin/cds.js +12 -0
  4. package/bin/compile/index.js +12 -2
  5. package/bin/deploy/index.js +6 -1
  6. package/bin/env.js +3 -2
  7. package/bin/init.js +8 -4
  8. package/bin/repl.js +3 -3
  9. package/lib/build/util.js +32 -20
  10. package/lib/compile/to-xsuaa.js +39 -26
  11. package/lib/deploy/to-hana/hana.js +62 -74
  12. package/lib/env/schemas/cds-rc.js +174 -2
  13. package/lib/import/asyncapi/parserContext.js +4 -30
  14. package/lib/import/odata/v2parser.js +3 -2
  15. package/lib/import/openapi/importOpenAPI.js +616 -569
  16. package/lib/import/openapi/utilities.js +7 -2
  17. package/lib/init/constants.js +2 -1
  18. package/lib/init/index.js +4 -0
  19. package/lib/init/projectReader.js +1 -2
  20. package/lib/init/registries/mta.js +8 -1
  21. package/lib/init/template/application-logging/files/package.json.hbs +6 -2
  22. package/lib/init/template/application-logging/index.js +4 -4
  23. package/lib/init/template/approuter/files/package.json +2 -2
  24. package/lib/init/template/cf-manifest/files/manifest.yml.hbs +3 -1
  25. package/lib/init/template/data/as-csv.js +1 -1
  26. package/lib/init/template/data/as-json.js +46 -42
  27. package/lib/init/template/enterprise-messaging-shared/index.js +1 -1
  28. package/lib/init/template/file-based-messaging/index.js +1 -1
  29. package/lib/init/template/hana/files/db/package.json +1 -1
  30. package/lib/init/template/hana/files/package.json.hbs +2 -0
  31. package/lib/init/template/hana/index.js +1 -1
  32. package/lib/init/template/handler/index.js +1 -1
  33. package/lib/init/template/html5-repo/files/app-package.json.hbs +2 -2
  34. package/lib/init/template/html5-repo/files/ui5-deploy.yaml.hbs +2 -2
  35. package/lib/init/template/http/index.js +6 -7
  36. package/lib/init/template/local-messaging/index.js +1 -1
  37. package/lib/init/template/nodejs/files/.vscode/extensions.json +0 -5
  38. package/lib/init/template/nodejs/files/.vscode/settings.json +1 -1
  39. package/lib/init/template/nodejs/files/.vscode/tasks.json +0 -2
  40. package/lib/init/template/nodejs/files/package.json.hbs +1 -1
  41. package/lib/init/template/postgres/index.js +1 -1
  42. package/lib/init/template/redis-messaging/index.js +1 -1
  43. package/lib/init/template/side-by-side-extensibility/files/mta.yaml.hbs +40 -0
  44. package/lib/init/template/side-by-side-extensibility/files/package.json.hbs +17 -0
  45. package/lib/init/template/side-by-side-extensibility/index.js +34 -0
  46. package/lib/init/template/typer/index.js +0 -1
  47. package/lib/init/template/typescript/files/package.json +2 -2
  48. package/lib/init/template/xsuaa/index.js +1 -1
  49. package/lib/mtx/push.js +0 -1
  50. package/lib/util/fs.js +1 -1
  51. package/lib/util/jsonc.js +1 -1
  52. package/lib/util/jsonc.pegjs +6 -2
  53. package/npm-shrinkwrap.json +74 -151
  54. package/package.json +1 -1
@@ -2,8 +2,13 @@ module.exports = { cdsName, nameFromPath, pathAndMethod, serviceName };
2
2
 
3
3
  function cdsName(oasName) {
4
4
  if (oasName === "") return "_";
5
+ if (typeof oasName === "number") return `_${JSON.stringify(oasName)}`;
5
6
  let name = oasName;
6
- if (name.match(/^(\p{Nd}|\p{Mn}|\p{Mc}|\p{Pc}|\p{Cf})/u)) name = "_" + name;
7
+ if (
8
+ !name.startsWith("_") &&
9
+ name.match(/^(\p{Nd}|\p{Mn}|\p{Mc}|\p{Pc}|\p{Cf})/u)
10
+ )
11
+ name = "_" + name;
7
12
  return name.replace(/[^_\p{L}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}]/gu, "_");
8
13
  //TODO: shorten if longer than 128 characters
9
14
  }
@@ -15,7 +20,7 @@ function nameFromPath(path, method) {
15
20
  .replace(/{[^}]+}/g, ".")
16
21
  .replace(/\/\./g, "_")
17
22
  .replace(/[^_\p{L}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}]/gu, "_");
18
- if (cleanPath === "") cleanPath = "_";
23
+ if (cleanPath === "") cleanPath = "_root";
19
24
  //TODO: shorten if longer than 128 characters
20
25
  return method === "get" ? cleanPath : `${cleanPath}_${method}`;
21
26
  }
@@ -5,7 +5,7 @@ module.exports = {
5
5
  COMMAND_ADD: 'add',
6
6
 
7
7
  JAVA_LTS_VERSIONS: [17, 21],
8
- MAVEN_ARCHETYPE_VERSION: '3.2.0',
8
+ MAVEN_ARCHETYPE_VERSION: '3.4.0',
9
9
 
10
10
  OPTIONS: Object.freeze({
11
11
  COMPLETION: 'completion',
@@ -24,6 +24,7 @@ module.exports = {
24
24
  MULTITENANCY: 'multitenancy',
25
25
  TOGGLES: 'toggles',
26
26
  EXTENSIBILITY: 'extensibility',
27
+ SIDE_BY_SIDE_EXTENSIBILITY: 'side-by-side-extensibility',
27
28
  MTX: 'mtx',
28
29
  XSUAA: 'xsuaa',
29
30
  HANA: 'hana',
package/lib/init/index.js CHANGED
@@ -265,6 +265,10 @@ module.exports = class CDSGenerator {
265
265
  // set is ordered ... always
266
266
  options.add = new Set(trimmedTokens);
267
267
 
268
+ if (options?.java) { // --java -> --add java
269
+ options.add.add('java')
270
+ }
271
+
268
272
  if (cds.cli.command === COMMAND_INIT && !options.add.has(JAVA)) {
269
273
  options.add.add(NODEJS);
270
274
  }
@@ -89,7 +89,7 @@ module.exports = new class ProjectReader {
89
89
  * @property {boolean} hasApprouter Indicates if the project uses the SAP Application Router.
90
90
  * @property {boolean} hasHtml5Repo Indicates if the project uses the SAP BTP HTML5 Application Repository.
91
91
  * @property {boolean} hasPortal Indicates if the project uses the SAP Cloud Portal service.
92
- * @property {boolean} hasWorkzone Indicates if the project uses the SAP Build Work Zone service.
92
+ * @property {boolean} hasWorkzoneStandard Indicates if the project uses the SAP Build Work Zone, Standard Edition service.
93
93
  * @property {boolean} hasDestination Indicates if the project uses the SAP BTP Destination service.
94
94
  * @property {boolean} hasMTXRoute Indicates if the approuter should have a multitenant route.
95
95
  * @property {boolean} hasMTXRouteJava Indicates if the approuter should have a multitenant subscription route (java).
@@ -118,7 +118,6 @@ module.exports = new class ProjectReader {
118
118
  const isApp = name => name !== 'appconfig' && !name.startsWith('_') && name !== 'router' && name !== 'portal'
119
119
  const reserved = {
120
120
  has: x => _inProd(x),
121
- shortcut: () => true,
122
121
  profile: () => cds.cli.options?.for,
123
122
  language: () => exists('pom.xml') ? 'java' : 'nodejs',
124
123
  isJava: () => _isJava(),
@@ -5,7 +5,8 @@ const srvNode4 = path => ({ in: 'modules', where: { type: 'nodejs', path } })
5
5
  const srvJava4 = path => ({ in: 'modules', where: { type: 'java', path } })
6
6
 
7
7
  // REVISIT: Clean up
8
- const isJava = process.argv[process.argv.indexOf('--add') + 1].split(',').includes('java') || cds.env['project-nature'] === 'java' || cds.cli.options?.add?.has('java')
8
+ const isJava = process.argv[process.argv.indexOf('--add') + 1].split(/[,\s+]/).includes('java') || cds.env['project-nature'] === 'java' || cds.cli.options?.add?.has('java') || process.argv.includes('--java')
9
+
9
10
  const srv4 = isJava ? srvJava4 : srvNode4
10
11
 
11
12
  const approuter = { in: 'modules', where: { type: 'approuter.nodejs' } }
@@ -25,6 +26,11 @@ const destinations = {
25
26
  where: { type: 'com.sap.application.content', 'build-parameters.no-source': true }
26
27
  }
27
28
 
29
+ const destinationSideBySideExtensibility = {
30
+ in: [destinations, 'parameters.content.subaccount.destinations' ],
31
+ where: { URL: '~{srv-api/srv-url}', Authentication: 'OAuth2ClientCredentials' }
32
+ }
33
+
28
34
  const connectivity = {
29
35
  in: 'resources',
30
36
  where: { type: 'org.cloudfoundry.managed-service', 'parameters.service': 'connectivity' }
@@ -186,6 +192,7 @@ module.exports = {
186
192
  xsuaa,
187
193
  destination,
188
194
  destinations,
195
+ destinationSideBySideExtensibility,
189
196
  connectivity,
190
197
  enterpriseMessaging,
191
198
  kafkaMessaging,
@@ -1,7 +1,11 @@
1
1
  {
2
+ {{#isNodejs}}
2
3
  "cds": {
4
+ {{/isNodejs}}
3
5
  "requires": {
4
- "application-logging": true
5
- }
6
+ "application-logging": true
7
+ }
8
+ {{#isNodejs}}
6
9
  }
10
+ {{/isNodejs}}
7
11
  }
@@ -1,6 +1,7 @@
1
1
  const { readProject } = require('../../projectReader')
2
2
  const { merge } = require('../../merge')
3
3
  const { srv4, mtxSidecar4, applicationLogging, approuter } = require('../../registries/mta')
4
+ const mvn = require('../../mvn')
4
5
 
5
6
  module.exports = class ApplicationLoggingTemplate extends require('../../plugin') {
6
7
 
@@ -20,10 +21,9 @@ module.exports = class ApplicationLoggingTemplate extends require('../../plugin'
20
21
 
21
22
  async run() {
22
23
  const project = readProject()
23
- const { isNodejs } = project
24
- if (isNodejs) {
25
- await merge(__dirname, 'files/package.json.hbs').into('package.json')
26
- }
24
+ const { isJava, configFile } = project
25
+ await merge(__dirname, 'files/package.json.hbs').into(configFile, { with: project })
26
+ if (isJava) await mvn.add('APPLICATION_LOGGING')
27
27
  }
28
28
 
29
29
  async combine() {
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "approuter",
3
3
  "dependencies": {
4
- "@sap/approuter": "^16.0.0"
4
+ "@sap/approuter": "^17.0.0"
5
5
  },
6
6
  "engines": {
7
- "node": "^20.0.0"
7
+ "node": "^22.0.0"
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node node_modules/@sap/approuter/approuter.js"
@@ -20,7 +20,9 @@ applications:
20
20
  disk_quota: 512M
21
21
  env:
22
22
  SPRING_PROFILES_ACTIVE: cloud
23
- buildpack: java_buildpack
23
+ JBP_CONFIG_COMPONENTS: "jres: ['com.sap.xs.java.buildpack.jdk.SAPMachineJDK']"
24
+ JBP_CONFIG_SAP_MACHINE_JDK: '{ version: 21.+ }'
25
+ buildpack: sap_java_buildpack_jakarta
24
26
  {{/isJava}}
25
27
  services:
26
28
  {{#hasHana}}
@@ -47,7 +47,7 @@ module.exports = async (data={}, csn, { headerOnly=false, separator=',' }={}) =>
47
47
  }
48
48
  else {
49
49
  if (i === 0) header.push(name)
50
- if (Array.isArray(value)) { // `array of` / `many`
50
+ if (Array.isArray(value) || typeof value === 'object') { // `array of`/`many` and `cds.Map`
51
51
  value = JSON.stringify(value)
52
52
  }
53
53
  value = escape(value)
@@ -10,35 +10,37 @@ const MAX_ASSOC_DEPTH = 5
10
10
  *
11
11
  * @param {Record<string, object[]>|undefined} data the data with entities and their records to be created
12
12
  * @param {object} csn the model
13
- * @param {Number} numRecords the number of records to be created for each entity
13
+ * @param {Number} numRecords the number of records to be created for each artifact
14
14
  * @param {{referenceData? : Record<string, string}} options misc. options
15
15
  * @returns {Record<string, object[]>} the filled data
16
16
  */
17
17
  const asJson = module.exports = async (data, csn, numRecords=1, options={}) => {
18
- let { referenceData, contentType } = options
18
+ let { referenceData, contentType, artifact='entity' } = options
19
19
 
20
- // if no entity restrictions are given, use all entities
21
- if (!data) data = csn.all('entity').reduce((all, e) => { all[e.name] = []; return all }, {})
20
+ // if no data is given, use all data
21
+ if (!data) {
22
+ data = csn.all(artifact).reduce((all, e) => { all[e.name] = []; return all }, {})
23
+ }
22
24
 
23
25
  // if no reference data is given, scan for it using cds.deploy.resources
24
26
  if (!referenceData) referenceData = await cds.deploy.resources(csn)
25
27
 
26
28
  // sort all entities first that are referenced by others, so that dependant entities can be created after them
27
29
  const { definitions: defs } = csn
28
- const entityNames = Object.keys(data).sort((n1, n2) => {
29
- const n1HasComp = Object.values(defs[n1].elements).some(el => el.type === 'cds.Composition')
30
- const n2HasComp = Object.values(defs[n2].elements).some(el => el.type === 'cds.Composition')
30
+ const names = Object.keys(data).sort((n1, n2) => {
31
+ const n1HasComp = Object.values(defs[n1].elements??[]).some(el => el.type === 'cds.Composition')
32
+ const n2HasComp = Object.values(defs[n2].elements??[]).some(el => el.type === 'cds.Composition')
31
33
  if (n1HasComp && !n2HasComp) return -1
32
34
  if (!n1HasComp && n2HasComp) return 1
33
35
  return 0
34
36
  })
35
37
 
36
38
  // requested entities shall always be created, so remove it from reference data
37
- Object.entries(referenceData).filter(([, name]) => entityNames.includes(name)).forEach(([file]) => delete referenceData[file])
39
+ Object.entries(referenceData).filter(([, name]) => names.includes(name)).forEach(([file]) => delete referenceData[file])
38
40
 
39
41
  addSemantics(csn)
40
42
 
41
- for (const name of entityNames) {
43
+ for (const name of names) {
42
44
  if (!data[name] // removed on purpose because records are created inline through compositions
43
45
  || data[name].length >= numRecords) { // numRecords already created by following associations
44
46
  continue
@@ -47,26 +49,26 @@ const asJson = module.exports = async (data, csn, numRecords=1, options={}) => {
47
49
  let numRecordsToGenerate // numRecords might be reduced if some records were created by following associations
48
50
  let records = []
49
51
  if (data[name].length > 0) {
50
- numRecordsToGenerate = numRecords - data[name].length
52
+ numRecordsToGenerate = numRecords - data[name].length
51
53
  records = Array(numRecordsToGenerate).fill({}).map(() => ({}))
52
54
  } else {
53
55
  numRecordsToGenerate = numRecords
54
56
  records = data[name] = Array(numRecordsToGenerate).fill({}).map(() => ({}))
55
57
  }
56
- const entity = defs[name]
58
+ const artifact = defs[name]
57
59
 
58
60
  const idNum = randomNumber()
59
61
  for (let i = 0; i < numRecordsToGenerate; i++) {
60
- fillRecord(records[i], idNum+i, entity, csn, data, null, 0, referenceData)
62
+ fillRecord(records[i], idNum+i, artifact, csn, data, null, 0, referenceData)
61
63
  }
62
- data[name] = data[name].concat(records) // append the new records to the possibly existingand referenced records
64
+ data[name] = data[name].concat(records) // append the new records to the possibly existing and referenced records
63
65
  // remove records w/ duplicate keys. This can happen if the key is an enum (and we have less enum values than requested records).
64
- data[name] = uniqRecords(data[name], entity)
66
+ data[name] = uniqRecords(data[name], artifact)
65
67
  }
66
68
  for (let [key, d] of Object.entries(data)) {
67
69
  if (contentType === 'json' && d.deleteReference && !d.keepReferece) {
68
70
  delete data[key]
69
- }
71
+ }
70
72
  delete d.keepReferece
71
73
  delete d.deleteReference
72
74
  uniqRecords(d, csn.definitions[key])
@@ -74,13 +76,13 @@ const asJson = module.exports = async (data, csn, numRecords=1, options={}) => {
74
76
  return data
75
77
  }
76
78
 
77
- function fillRecord(record, recordID, entity, csn, data, parentEntity, depth, referenceData) {
79
+ function fillRecord(record, recordID, artifact, csn, data, parent, depth, referenceData) {
78
80
  Object.defineProperty(record, '$id', {value: recordID, enumerable: false})
79
- if (data[entity.name]) data[entity.name].push(record) // add id to data, to be referenced by associations to prevent circular dependencies
81
+ if (data[artifact.name]) data[artifact.name].push(record) // add id to data, to be referenced by associations to prevent circular dependencies
80
82
 
81
- const { elements } = entity
82
- Object.values(elements).sort(asJson.elementsSorter(csn)).forEach(el => {
83
- record[el.name] = dataForField(el, entity, csn, record, data, parentEntity, depth, referenceData)
83
+ const elems = Object.values(artifact.elements ?? artifact.params).sort(asJson.elementsSorter(csn))
84
+ elems.forEach(el => {
85
+ record[el.name] = dataForField(el, artifact, csn, record, data, parent, depth, referenceData)
84
86
  })
85
87
  return record
86
88
  }
@@ -110,7 +112,7 @@ asJson.elementsSorter = function (csn) {
110
112
  }
111
113
 
112
114
 
113
- function dataForField(element, entity, csn, record, data, parentEntity, depth, referenceData) {
115
+ function dataForField(element, artifact, csn, record, data, parent, depth, referenceData) {
114
116
  if (element.virtual || (element['@Core.Computed'] && element.value))
115
117
  return
116
118
  if (record[element.name] !== undefined)
@@ -119,12 +121,12 @@ function dataForField(element, entity, csn, record, data, parentEntity, depth, r
119
121
  if (element.items?.elements) // `array of` / `many` with complex type
120
122
  return [ fillRecord({}, record['$id'], element.items, csn, data, null, depth+1, referenceData) ]
121
123
  else if (element.items?.type) // `array of` / `many` with simple type
122
- return [ dataForField(element.items, entity, csn, record, data, null, depth+1, referenceData) ]
124
+ return [ dataForField(element.items, artifact, csn, record, data, null, depth+1, referenceData) ]
123
125
 
124
126
  if (element.is_struct) {
125
127
  let struct = {}
126
128
  for (const el of element.elements) {
127
- struct[el.name] = dataForField(el, entity, csn, record, data, null, depth+1, referenceData)
129
+ struct[el.name] = dataForField(el, artifact, csn, record, data, null, depth+1, referenceData)
128
130
  }
129
131
  return struct
130
132
  }
@@ -166,11 +168,11 @@ function dataForField(element, entity, csn, record, data, parentEntity, depth, r
166
168
  const enumName = randomFromList(Object.keys(element.enum))
167
169
  const value = element.enum[enumName].val ?? element.enum[enumName].value ?? enumName
168
170
  // key of common.CodeList, also fill name and descr with enum name
169
- if (element.key && entity.elements.name && entity.elements.descr)
171
+ if (element.key && artifact.elements.name && artifact.elements.descr)
170
172
  record.name = record.descr = enumName
171
173
  return value
172
174
  }
173
- if (entity['@Communication.Contact.email']?.some(mail => mail.address?.['='] === element.name)
175
+ if (artifact['@Communication.Contact.email']?.some(mail => mail.address?.['='] === element.name)
174
176
  || element['@Communication.IsEmailAddress']
175
177
  || element['@cds.on.insert']?.['='] === '$user'
176
178
  || element['@cds.on.update']?.['='] === '$user') {
@@ -180,9 +182,9 @@ function dataForField(element, entity, csn, record, data, parentEntity, depth, r
180
182
  let str = `${element.name}-${record['$id']}`
181
183
 
182
184
  if (element.key) {
183
- str = `${entity.name.split('.').pop()}-${record['$id']}`
184
- if (element.name === 'locale' && entity.name.endsWith('texts')) {
185
- const record = randomFromReferenceData(entity, csn, referenceData)
185
+ str = `${artifact.name.split('.').pop()}-${record['$id']}`
186
+ if (element.name === 'locale' && artifact.name.endsWith('texts')) {
187
+ const record = randomFromReferenceData(artifact, csn, referenceData)
186
188
  if (record) return record.locale
187
189
  return randomLanguage()
188
190
  }
@@ -237,15 +239,15 @@ function dataForField(element, entity, csn, record, data, parentEntity, depth, r
237
239
  }
238
240
  case 'cds.Composition': {
239
241
  const target = csn.definitions[element.target]
240
- if (!target || entity.name === target.name || depth > MAX_ASSOC_DEPTH) return
241
-
242
+ if (!target || artifact.name === target.name || depth > MAX_ASSOC_DEPTH) return
243
+
242
244
  let targetRecords = [] // in findKeysInOnCondition target records are linked to the entity, we don't want to overwrite any existing data
243
245
  if (data[target.name]) data[target.name].deleteReference = true // for json composites references are nested and will be deleted when writing to file
244
-
246
+
245
247
  const targetRecordCount = element.is2many ? 2 : 1 // TODO: make this configurable
246
248
  const targetRecordID = randomNumber()
247
249
  for (let i = 0; i < targetRecordCount; i++) {
248
- const newRecord = fillRecord({}, targetRecordID+i, target, csn, data, entity, depth+1, referenceData)
250
+ const newRecord = fillRecord({}, targetRecordID+i, target, csn, data, artifact, depth+1, referenceData)
249
251
  if (newRecord) targetRecords.push(newRecord)
250
252
  }
251
253
 
@@ -259,8 +261,8 @@ function dataForField(element, entity, csn, record, data, parentEntity, depth, r
259
261
  }
260
262
  uniqRecords(targetRecords, target)
261
263
 
262
- // set the entire target record inline
263
- return element.is2many ? targetRecords : targetRecords[0]
264
+ // consistent return data type: array
265
+ return element.is2many ? targetRecords : [targetRecords[0]]
264
266
  }
265
267
  case 'cds.Association': {
266
268
  if (element.is2many)
@@ -280,16 +282,16 @@ function dataForField(element, entity, csn, record, data, parentEntity, depth, r
280
282
  targetRecords = data[target.name] = []
281
283
  else if (!targetRecords) // target entity is not in the list of entities to be created
282
284
  return
283
-
285
+
284
286
  if (targetRecords.length > 0) { // use record generated by following asociation
285
287
  targetRecord = targetRecords.at(-1) // make sure the localized record references the newly generated texts
286
288
  } else {
287
- targetRecord = fillRecord({}, randomNumber(), target, csn, data, entity, depth+1, referenceData)
289
+ targetRecord = fillRecord({}, randomNumber(), target, csn, data, artifact, depth+1, referenceData)
288
290
  if (targetRecord) {
289
291
  targetRecords.push(targetRecord)
290
292
  uniqRecords(targetRecords, target)
291
293
  }
292
- if (parentEntity && (parentEntity.name != entity.name)) data[target.name].keepReferece = true
294
+ if (parent && (parent.name != artifact.name)) data[target.name].keepReferece = true
293
295
  }
294
296
  }
295
297
 
@@ -307,8 +309,10 @@ function dataForField(element, entity, csn, record, data, parentEntity, depth, r
307
309
  return
308
310
  case 'cds.Vector': // TODO add Vector
309
311
  return
312
+ case 'cds.Map':
313
+ return {}
310
314
  default: {
311
- console.error(`Unknown type ${element._type} (${element.type}) in '${entity.name}:${element.name}'`)
315
+ console.error(`Unknown type ${element._type} (${element.type}) in '${artifact.name}:${element.name}'`)
312
316
  return
313
317
  }
314
318
  }
@@ -393,14 +397,14 @@ function findKeysInOnCondition(assocElement, target) {
393
397
  return [ownKey, ownKeyStructured, targetKey]
394
398
  }
395
399
 
396
- function uniqRecords(records, entity) {
397
- const keys = Object.keys(entity.keys||{})
398
- if (keys.length === 0) return records
400
+ function uniqRecords(records, artifact) {
401
+ const keys = Object.keys(artifact.keys||{})
402
+ if (keys.length === 0) keys.push('$id') // no keys (e.g. in actions) -> use record's $id
399
403
  const keySet = new Set()
400
404
  for (let i = 0; i < records.length; i++) {
401
405
  const key = keys.map(k => records[i][k]).join('|')
402
406
  if (keySet.has(key)) {
403
- // console.debug('Removing', keys, key, 'from', entity.name)
407
+ // console.debug('Removing', keys, key, 'from', artifact.name)
404
408
  records.splice(i, 1)
405
409
  i--
406
410
  }
@@ -34,7 +34,7 @@ module.exports = class EnterpriseMessagingSharedTemplate extends require('../../
34
34
  const project = readProject()
35
35
  const { configFile, isNodejs } = project
36
36
  if (isNodejs) {
37
- project.shortcut = !(await read(configFile)).cds?.requires?.messaging?.kind
37
+ project.shortcut = typeof (await read(configFile)).cds?.requires?.messaging !== 'object'
38
38
  project.cloudevents = cds.cli.options.cloudevents
39
39
  await merge(__dirname, 'files/package.json.hbs').into(configFile, { with: project })
40
40
  }
@@ -21,7 +21,7 @@ module.exports = class FileBasedMessagingTemplate extends require('../../plugin'
21
21
  async run() {
22
22
  const project = readProject()
23
23
  const { configFile } = project
24
- project.shortcut = !(await read(configFile)).cds?.requires?.messaging?.kind
24
+ project.shortcut = typeof (await read(configFile)).cds?.requires?.messaging !== 'object'
25
25
  await merge(__dirname, 'files/package.json.hbs').into(configFile, { with: project })
26
26
  }
27
27
  }
@@ -5,7 +5,7 @@
5
5
  "@sap/hdi-deploy": "^5"
6
6
  },
7
7
  "engines": {
8
- "node": "^20.0.0"
8
+ "node": "^22.0.0"
9
9
  },
10
10
  "scripts": {
11
11
  "start": "node node_modules/@sap/hdi-deploy/deploy.js --use-hdb",
@@ -1,7 +1,9 @@
1
1
  {
2
+ {{#isNodejs}}
2
3
  "dependencies": {
3
4
  "@cap-js/hana": "^1"
4
5
  },
6
+ {{/isNodejs}}
5
7
  {{#isNodejs}}
6
8
  "cds": {
7
9
  {{/isNodejs}}
@@ -20,7 +20,7 @@ module.exports = class HanaTemplate extends require('../../plugin') {
20
20
  async run() {
21
21
  const project = readProject()
22
22
  const { isJava, configFile, db } = project
23
- project.shortcut = !(await read(configFile)).cds?.requires?.db?.kind
23
+ project.shortcut = typeof (await read(configFile)).cds?.requires?.db !== 'object'
24
24
 
25
25
  // REVISIT: Let cds build generate those by default
26
26
  await merge(__dirname, 'files/src/.hdiconfig').into(join(db.folder, 'src/.hdiconfig'))
@@ -5,7 +5,7 @@ const mvn = require('../../mvn')
5
5
  module.exports = class Handler extends require('../../plugin') {
6
6
 
7
7
  static help() {
8
- return 'add handler stubs for actions and functions (Java)'
8
+ return 'add handler stubs for actions and functions (Java)'
9
9
  }
10
10
 
11
11
  options() {
@@ -8,13 +8,13 @@
8
8
  "start": "ui5 serve"
9
9
  },
10
10
  "devDependencies": {
11
- {{#hasWorkzone}}"@sap/ui5-builder-webide-extension": "^1.1.9",{{/hasWorkzone}}
11
+ {{#hasWorkzoneStandard}}"@sap/ui5-builder-webide-extension": "^1.1.9",{{/hasWorkzoneStandard}}
12
12
  "@ui5/cli": "^4",
13
13
  "ui5-task-zipper": "^3"
14
14
  },
15
15
  "ui5": {
16
16
  "dependencies": [
17
- {{#hasWorkzone}}"@sap/ui5-builder-webide-extension",{{/hasWorkzone}}
17
+ {{#hasWorkzoneStandard}}"@sap/ui5-builder-webide-extension",{{/hasWorkzoneStandard}}
18
18
  "ui5-task-zipper"
19
19
  ]
20
20
  }
@@ -13,13 +13,13 @@ builder:
13
13
  - "/test/**"
14
14
  - "/localService/**"
15
15
  customTasks:
16
- {{#hasWorkzone}}
16
+ {{#hasWorkzoneStandard}}
17
17
  - name: webide-extension-task-updateManifestJson
18
18
  afterTask: replaceVersion
19
19
  configuration:
20
20
  appFolder: webapp
21
21
  destDir: dist
22
- {{/hasWorkzone}}
22
+ {{/hasWorkzoneStandard}}
23
23
  - name: ui5-task-zipper
24
24
  afterTask: generateVersionInfo
25
25
  configuration:
@@ -130,9 +130,12 @@ module.exports = class HttpTemplate extends require('../../plugin') {
130
130
  for (const e of service.entities) {
131
131
  payload[e.name] = []
132
132
  }
133
+ for (const a of service.actions) {
134
+ payload[a.name] = []
135
+ }
133
136
  }
134
137
  // csn.all('entity').reduce((all, e) => { all[e.name] = []; return all }, {})
135
- await asJson(payload, csn, 1)
138
+ await asJson(payload, csn, 1, {referenceData: []})
136
139
  for (const p in payload) {
137
140
  delete payload[p][0].texts
138
141
  delete payload[p][0].localized
@@ -195,12 +198,8 @@ module.exports = class HttpTemplate extends require('../../plugin') {
195
198
 
196
199
  let actionPayload = {}
197
200
  if (a.params) {
198
- for (const [key, value] of Object.entries(a.params)) {
199
- actionPayload[key] = value.type === 'cds.Integer' ? 1
200
- : value.type === 'string' ? 'sample string'
201
- // TODO reference an object that has been generated
202
- : typeof value.type === 'object' && value.type.ref ? payload[value.type.ref[0]][0][value.type.ref[1]]
203
- : null
201
+ for (const pName of Object.keys(a.params)) {
202
+ actionPayload[pName] = payload[a.name]?.[0]?.[pName] ?? null
204
203
  }
205
204
  }
206
205
  requests.push(`\n### ${simpleNameAction}\n# @name ${simpleNameAction}_POST\nPOST ${url}\n${headers}\n\n${JSON.stringify(actionPayload, null, 2)}\n`)
@@ -22,7 +22,7 @@ module.exports = class LocalMessagingTemplate extends require('../../plugin') {
22
22
  async run() {
23
23
  const project = readProject()
24
24
  const { configFile } = project
25
- project.shortcut = !(await read(configFile)).cds?.requires?.messaging?.kind
25
+ project.shortcut = typeof (await read(configFile)).cds?.requires?.messaging !== 'object'
26
26
  await merge(__dirname, 'files/package.json.hbs').into(configFile, { with: project })
27
27
  }
28
28
  }
@@ -1,8 +1,4 @@
1
1
  {
2
- // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
3
- // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
4
-
5
- // List of extensions which should be recommended for users of this workspace.
6
2
  "recommendations": [
7
3
  "SAPSE.vscode-cds",
8
4
  "dbaeumer.vscode-eslint",
@@ -11,7 +7,6 @@
11
7
  "qwtel.sqlite-viewer",
12
8
  "humao.rest-client"
13
9
  ],
14
- // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
15
10
  "unwantedRecommendations": [
16
11
 
17
12
  ]
@@ -8,4 +8,4 @@
8
8
  // "tab",
9
9
  // "tsv"
10
10
  ]
11
- }
11
+ }
@@ -1,6 +1,4 @@
1
1
  {
2
- // See https://go.microsoft.com/fwlink/?LinkId=733558
3
- // for the documentation about the tasks.json format
4
2
  "version": "2.0.0",
5
3
  "tasks": [
6
4
  {
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "devDependencies": {
13
13
  "@cap-js/sqlite": "^1",
14
- "@cap-js/cds-types": "^0.6"
14
+ "@cap-js/cds-types": "^0.7.0"
15
15
  },
16
16
  "scripts": {
17
17
  "start": "cds-serve"
@@ -24,7 +24,7 @@ module.exports = class PostgresTemplate extends require('../../plugin') {
24
24
  async run() {
25
25
  const project = readProject()
26
26
  const { configFile, isJava } = project
27
- project.shortcut = !(await read(configFile)).cds?.requires?.db?.kind
27
+ project.shortcut = typeof (await read(configFile)).cds?.requires?.db !== 'object'
28
28
  await merge(__dirname, 'files/package.json.hbs').into('package.json', { project })
29
29
  if (isJava) await mvn.add('postgresql')
30
30
  }
@@ -28,7 +28,7 @@ module.exports = class RedisMessagingTemplate extends require('../../plugin') {
28
28
  async run() {
29
29
  const project = readProject()
30
30
  const { configFile } = project
31
- project.shortcut = !(await read(configFile)).cds?.requires?.messaging?.kind
31
+ project.shortcut = typeof (await read(configFile)).cds?.requires?.messaging !== 'object'
32
32
  await merge(__dirname, 'files/package.json.hbs').into(configFile, { project })
33
33
  }
34
34
  }
@@ -0,0 +1,40 @@
1
+ ---
2
+ modules:
3
+ - name: {{appName}}-srv
4
+ type: {{language}}
5
+ path: {{& srvPath}}
6
+ provides:
7
+ - name: srv-api # required by consumers of CAP services (e.g. approuter)
8
+ properties:
9
+ srv-url: ${default-url}
10
+
11
+ - name: {{appName}}-destinations
12
+ type: com.sap.application.content
13
+ requires:
14
+ - name: {{appName}}-auth
15
+ parameters:
16
+ service-key:
17
+ name: {{appName}}-auth-key
18
+ - name: srv-api
19
+ - name: {{appName}}-destination
20
+ parameters:
21
+ content-target: true
22
+ build-parameters:
23
+ no-source: true
24
+ parameters:
25
+ content:
26
+ subaccount:
27
+ existing_destinations_policy: update
28
+ destinations:
29
+ - Name: {{appName}}-side-by-side-extensibility
30
+ URL: ~{srv-api/srv-url}
31
+ Authentication: OAuth2ClientCredentials
32
+ TokenServiceInstanceName: {{appName}}-auth
33
+ TokenServiceKeyName: {{appName}}-auth-key
34
+
35
+ resources:
36
+ - name: {{appName}}-destination
37
+ type: org.cloudfoundry.managed-service
38
+ parameters:
39
+ service: destination
40
+ service-plan: lite