@sap/cds-dk 8.0.3 → 8.1.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 +51 -1
  2. package/bin/bind.js +3 -3
  3. package/bin/cds.js +11 -15
  4. package/bin/init.js +3 -6
  5. package/bin/version.js +7 -6
  6. package/lib/build/buildTaskFactory.js +29 -12
  7. package/lib/build/constants.js +1 -1
  8. package/lib/build/provider/hana/index.js +5 -6
  9. package/lib/build/provider/internalBuildTaskProvider.js +2 -35
  10. package/lib/build/provider/mtx/resourcesTarBuilder.js +2 -2
  11. package/lib/build/provider/mtx-sidecar/index.js +10 -3
  12. package/lib/cds.js +1 -0
  13. package/lib/compile/to-mermaid.js +27 -12
  14. package/lib/compile/to_openapi.js +2 -1
  15. package/lib/deploy/to-hana/hana.js +10 -8
  16. package/lib/deploy/to-hana/hdiDeployUtil.js +10 -1
  17. package/lib/import/common.js +1 -1
  18. package/lib/import/message.js +2 -2
  19. package/lib/import/odata/v2parser.js +2 -2
  20. package/lib/init/bas.js +73 -32
  21. package/lib/init/constants.js +5 -2
  22. package/lib/init/index.js +6 -4
  23. package/lib/init/merge.js +5 -3
  24. package/lib/init/projectReader.js +3 -4
  25. package/lib/init/template/audit-logging/files/package.sidecar.json +7 -0
  26. package/lib/init/template/audit-logging/index.js +1 -1
  27. package/lib/init/template/cf-manifest/index.js +4 -0
  28. package/lib/init/template/data/as-json.js +22 -12
  29. package/lib/init/template/data/index.js +1 -1
  30. package/lib/init/template/helm-unified-runtime/index.js +3 -1
  31. package/lib/init/template/http/index.js +37 -28
  32. package/lib/init/template/multitenancy/index.js +9 -31
  33. package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +0 -1
  34. package/lib/init/template/sample/files/app/browse/webapp/manifest.json +0 -1
  35. package/lib/init/template/sample/files/nodejs/admin-service.js +13 -8
  36. package/lib/init/template/sample/files/nodejs/cat-service.js +27 -15
  37. package/lib/init/template/sample/files/srv/cat-service.cds +5 -1
  38. package/lib/init/template/typescript/files/package.json +3 -3
  39. package/lib/init/template/workzone/index.js +0 -3
  40. package/lib/init/template/xsuaa/index.js +10 -3
  41. package/lib/mtx/push.js +3 -1
  42. package/npm-shrinkwrap.json +19 -16
  43. package/package.json +1 -1
@@ -355,7 +355,7 @@ function _extractAllFunctionImports(functionImports, parserContext) {
355
355
  }
356
356
 
357
357
  function _replaceSpecialCharacters(text) {
358
- return text.replace(/(?:\\[rn]|[\r\n]+)+/gm, "\\n").replace(/\s+/g, ' ').replace(/"/g, """).trim();
358
+ return text.replace(/\\/g, "\\\\").replace(/(?:\\[rn]|[\r\n]+)+/gm, "\\n").replace(/\s+/g, ' ').replace(/"/g, """).trim();
359
359
  }
360
360
 
361
361
  // to parse the documentation tag
@@ -972,7 +972,7 @@ function _parseEntityProperty(properties, parserContext) {
972
972
  // Has more than one entities
973
973
  for (let i = 0; i < properties.length; i++) {
974
974
  propAttributes = properties[i]._attributes;
975
- // if the property contains documentatin tag
975
+ // if the property contains documentation tag
976
976
  if (properties[i] && properties[i].Documentation)
977
977
  propAttributes.doc = _parseDocumentationTag(properties[i].Documentation);
978
978
  retProperties[propAttributes.Name] = _getPropertyAttributes(propAttributes, parserContext);
package/lib/init/bas.js CHANGED
@@ -5,8 +5,10 @@ const { bold } = require('../util/term')
5
5
  const { OPTIONS } = require('./constants')
6
6
  const {
7
7
  NODEJS, JAVA,
8
- HANA, APPROUTER, XSUAA, APPLICATION_LOGGING, ENTERPRISE_MESSAGING,
9
- MTA, MULTITENANCY, PIPELINE,
8
+ XSUAA, APPROUTER, CONNECTIVITY, DESTINATION, HTML5_REPO, PORTAL, APPLICATION_LOGGING, AUDIT_LOGGING, NOTIFICATIONS,
9
+ ENTERPRISE_MESSAGING, KAFKA, REDIS_MESSAGING,
10
+ HANA, POSTGRES,
11
+ MTA, HELM, CONTAINERIZE, MULTITENANCY, PIPELINE,
10
12
  TINY_SAMPLE, SAMPLE
11
13
  } = OPTIONS
12
14
  const cwd = process.cwd()
@@ -17,75 +19,114 @@ module.exports = [{
17
19
  default: '',
18
20
  target: 'projectName',
19
21
  validate: async projectName => {
20
- try {
21
- cds.root = path.resolve(cwd, projectName || '.')
22
- validate.projectName(projectName)
23
- validate.projectFolder(path.join(cwd, projectName), cwd)
24
- } catch (e) {
25
- return e // BAS Yeoman generator requires error to be returned
26
- }
27
- return true
22
+ try {
23
+ cds.root = path.resolve(cwd, projectName || '.')
24
+ validate.projectName(projectName)
25
+ validate.projectFolder(path.join(cwd, projectName), cwd)
26
+ } catch (e) {
27
+ return e // BAS Yeoman generator requires error to be returned
28
+ }
29
+ return true
28
30
  },
29
31
  guiOptions: {
30
- hint: 'This name is restricted to certain characters. A valid project name is compatible across platforms.',
31
- mandatory: true
32
+ breadcrumb: 'Project Name',
33
+ hint: 'This name is restricted to certain characters. A valid project name is compatible across platforms.',
34
+ mandatory: true
32
35
  }
33
36
  }, {
34
37
  title: 'Type',
35
38
  description: `Select your runtime.`,
36
39
  default: NODEJS,
37
40
  choices: [
38
- { name: 'Node.js', value: NODEJS },
39
- { name: 'Java', value: JAVA }
41
+ { name: 'Node.js', value: NODEJS },
42
+ { name: 'Java', value: JAVA }
40
43
  ],
41
44
  multiple: false,
42
45
  target: 'add',
43
46
  guiOptions: {
44
- hint: `The preferred runtime can't be changed afterwards. To use a different runtime, create a new project.`,
45
- mandatory: true
47
+ breadcrumb: 'Runtime',
48
+ hint: `The preferred runtime can't be changed afterwards. To use a different runtime, create a new project.`,
49
+ mandatory: true
46
50
  }
47
51
  }, {
48
- title: 'Features',
49
- description: 'Choose productive runtime capabilities for your application.',
52
+ title: 'Databases',
53
+ description: 'Choose a database for your application.',
50
54
  default: '',
51
55
  choices: [
52
- { name: 'SAP HANA Cloud', value: HANA },
53
- { name: 'SAP Application Router', value: APPROUTER },
54
- { name: 'User Authentication via XSUAA', value: XSUAA },
55
- { name: 'Application Logging', value: APPLICATION_LOGGING },
56
- { name: 'SAP BTP Event Mesh', value: ENTERPRISE_MESSAGING }
56
+ { name: 'SAP HANA Cloud', value: HANA },
57
+ { name: 'PostgreSQL', value: POSTGRES }
57
58
  ],
58
59
  multiple: true,
59
60
  target: 'add',
60
61
  guiOptions: {
61
- hint: `Choose any combination of features enriching the capabilities of your project. You can also add features later with ${bold('cds add')}`
62
+ breadcrumb: 'Databases',
63
+ hint: `Choose any combination of features enriching the capabilities of your project. You can also add features later with ${bold('cds add')}`
62
64
  }
63
65
  }, {
64
66
  title: 'Deployment',
65
67
  description: 'Choose which way to deploy your project.',
66
68
  default: '',
67
69
  choices: [
68
- { name: 'Cloud Foundry: MTA Deployment', value: MTA },
69
- // { name: 'Kyma: Helm Deployment', value: HELM },
70
- { name: 'Multitenancy', value: MULTITENANCY },
71
- { name: 'CI/CD Pipeline Integration', value: PIPELINE },
70
+ { name: 'Cloud Foundry: MTA Deployment', value: MTA },
71
+ { name: 'Kyma: Helm Deployment', value: HELM },
72
+ { name: 'Kyma: Containerization using ctz CLI', value: CONTAINERIZE },
73
+ { name: 'Multitenancy', value: MULTITENANCY },
74
+ { name: 'CI/CD Pipeline Integration', value: PIPELINE }
75
+ ],
76
+ multiple: true,
77
+ target: 'add',
78
+ guiOptions: {
79
+ breadcrumb: 'Deployment',
80
+ hint: `Choose any combination of features enriching the capabilities of your project. You can also add features later with ${bold('cds add')}`
81
+ }
82
+ }, {
83
+ title: 'Runtime Features',
84
+ description: 'Choose productive runtime capabilities for your application.',
85
+ default: '',
86
+ choices: [
87
+ { name: 'SAP BTP Authorization and Trust Management Service (XSUAA)', value: XSUAA },
88
+ { name: 'SAP BTP Application Router', value: APPROUTER },
89
+ { name: 'SAP BTP Connectivity Service', value: CONNECTIVITY },
90
+ { name: 'SAP BTP Destination Service', value: DESTINATION },
91
+ { name: 'SAP BTP HTML5 Application Repository', value: HTML5_REPO },
92
+ { name: 'SAP BTP Portal Service', value: PORTAL },
93
+ { name: 'SAP BTP Application Logging Service', value: APPLICATION_LOGGING },
94
+ { name: 'SAP BTP Audit Logging Service', value: AUDIT_LOGGING },
95
+ { name: 'SAP BTP Notification Service', value: NOTIFICATIONS }
96
+ ],
97
+ multiple: true,
98
+ target: 'add',
99
+ guiOptions: {
100
+ breadcrumb: 'Runtime Features',
101
+ hint: `Choose any combination of features enriching the capabilities of your project. You can also add features later with ${bold('cds add')}`
102
+ }
103
+ }, {
104
+ title: 'Messaging',
105
+ description: 'Choose messaging capabilities for your application.',
106
+ default: '',
107
+ choices: [
108
+ { name: 'SAP BTP Event Mesh', value: ENTERPRISE_MESSAGING },
109
+ { name: 'Apache Kafka', value: KAFKA },
110
+ { name: 'Redis', value: REDIS_MESSAGING }
72
111
  ],
73
112
  multiple: true,
74
113
  target: 'add',
75
114
  guiOptions: {
76
- hint: `Choose any combination of features enriching the capabilities of your project. You can also add features later with ${bold('cds add')}`
115
+ breadcrumb: 'Messaging',
116
+ hint: `Choose any combination of features enriching the capabilities of your project. You can also add features later with ${bold('cds add')}`
77
117
  }
78
118
  }, {
79
119
  title: 'Samples',
80
120
  description: `Add sample content to your project.`,
81
121
  default: '',
82
122
  choices: [
83
- { name: 'Minimal Sample', value: TINY_SAMPLE },
84
- { name: 'Extended Sample with UI', value: SAMPLE }
123
+ { name: 'Minimal Sample', value: TINY_SAMPLE },
124
+ { name: 'Extended Sample with UI', value: SAMPLE }
85
125
  ],
86
126
  multiple: true,
87
127
  target: 'add',
88
128
  guiOptions: {
89
- hint: 'Speed up your ramp-up phase with sample files in your project.'
129
+ breadcrumb: 'Samples',
130
+ hint: 'Speed up your ramp-up phase with sample files in your project.'
90
131
  }
91
132
  }]
@@ -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.0.0',
8
+ MAVEN_ARCHETYPE_VERSION: '3.1.1',
9
9
 
10
10
  OPTIONS: Object.freeze({
11
11
  COMPLETION: 'completion',
@@ -69,7 +69,10 @@ module.exports = {
69
69
  SAMPLES: 'https://github.com/SAP-samples/cloud-cap-samples',
70
70
  CAPIRE: 'https://cap.cloud.sap',
71
71
  MAVEN_ARCHETYPE_HELP: `https://cap.cloud.sap/docs/java/developing-applications/building#the-maven-archetype`,
72
- MAVEN_INSTALL_HELP: `https://maven.apache.org/install.html`
72
+ MAVEN_INSTALL_HELP: `https://maven.apache.org/install.html`,
73
+ CAP_JS_COMMUNITY_ODATA_V2: 'https://github.com/cap-js-community/odata-v2-adapter',
74
+ CAP_JS_COMMUNITY_EVENT_QUEUE: 'https://github.com/cap-js-community/event-queue',
75
+ CAP_JS_COMMUNITY_WEBSOCKET: 'https://github.com/cap-js-community/websocket'
73
76
  }),
74
77
 
75
78
  PROJECT_FILES: ['package.json', 'pom.xml', '.cdsrc.json']
package/lib/init/index.js CHANGED
@@ -22,14 +22,16 @@ module.exports = class CDSGenerator {
22
22
  }
23
23
 
24
24
  static help({ exclude = [] } = {}) {
25
+ if (!cds.add) return // use case shell completion: cds.add is undefined and not needed
26
+
25
27
  const plugins = this.readPlugins(exclude)
26
28
  const nameFixedLength = Math.max(...plugins.map(plugin => plugin.name.length))
27
29
  return plugins
30
+ .filter(({module}) => module.help())
28
31
  .map(({name, module}) => {
29
- const help = module.help?.() ?? ''
30
- return ` *${name}*${' '.repeat(nameFixedLength - name.length)} - ${help}`
32
+ return ` *${name}*${' '.repeat(nameFixedLength - name.length)} - ${module.help()}`
31
33
  })
32
- .join('\n\n')
34
+ .join('\n')
33
35
  }
34
36
 
35
37
  /**
@@ -176,7 +178,7 @@ module.exports = class CDSGenerator {
176
178
  const fuzzySearch = require('../../bin/util/fuzzySearch')
177
179
  const [bestMatch] = fuzzySearch(plugin, entries.map(e => e.name))
178
180
  const max = entries.reduce((max, {name}) => Math.max(max, name.length), 0)
179
- const allFacetsText = entries.map(({name, module}) => {
181
+ const allFacetsText = entries.filter(({module})=> module.help()).map(({name, module}) => {
180
182
  const help = module.help?.() ?? ''
181
183
  return `${term.bold(name) + ' '.repeat(max - name.length)} ${term.dim(help)}`
182
184
  }).join('\n')
package/lib/init/merge.js CHANGED
@@ -70,7 +70,8 @@ module.exports.mergeGitignore = async function(into, from) {
70
70
  /**
71
71
  * @typedef {Object} Deletion
72
72
  * @property {MatchedNode} item - matched node to delete
73
- * @property {Object[]} relationships - list of relationships to delete
73
+ * @property {MatchedNode} [key] - key path to property to delete in node (optional)
74
+ * @property {Object[]} [relationships] - list of relationships to delete
74
75
  * @property {string} relationships[].removeProperty - key path to to-be-removed property
75
76
  * @property {string[]} relationships[].allWithin - key path to relationship
76
77
  */
@@ -285,10 +286,11 @@ async function _mergeYAML(into, from, project, semantics = {}) {
285
286
  })
286
287
 
287
288
  // 4. Delete entries from the project (e.g. separate deployer module when adding mtx)
288
- deletions?.forEach(({ item, relationships }) => {
289
+ deletions?.forEach(({ item, relationships, key }) => {
289
290
  const entry = entryMap.get(item)
290
291
  if (!entry) return
291
- entry.collection.delete(entry.index)
292
+ if (key) entry.collection.deleteIn([entry.index, ...key.split('.')])
293
+ else entry.collection.delete(entry.index)
292
294
 
293
295
  relationships?.forEach(relationship => {
294
296
  const [allWithinKeyPath, inKeyPath, into] = relationship.allWithin
@@ -3,7 +3,7 @@ const { join, resolve, basename } = require('path')
3
3
  const { fs, exists, isdir, path } = cds.utils
4
4
  const { parseXml } = require('./xml')
5
5
  const { REGEX_JAVA_VERSION, JAVA_LTS_VERSIONS } = require('./constants')
6
- const DEBUG = cds.debug('cli')
6
+ const TRACE = cds.debug('trace')
7
7
 
8
8
  module.exports = new class ProjectReader {
9
9
 
@@ -88,7 +88,7 @@ module.exports = new class ProjectReader {
88
88
  * @property {boolean} hasAttachments Indicates if the project uses SAP BTP Object Store Service.
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
- * @property {boolean} hasPortal Indicates if the project uses the SAP Cloud Portal serivce.
91
+ * @property {boolean} hasPortal Indicates if the project uses the SAP Cloud Portal service.
92
92
  * @property {boolean} hasDestination Indicates if the project uses the SAP BTP Destination service.
93
93
  * @property {boolean} hasMTXRoute Indicates if the approuter should have a multitenant route.
94
94
  * @property {boolean} hasMTXRouteJava Indicates if the approuter should have a multitenant subscription route (java).
@@ -106,7 +106,7 @@ module.exports = new class ProjectReader {
106
106
  if (!this.appName) this.#setPackageInfo()
107
107
  const env = this.env4('production')
108
108
  const { appVersion, appName, appId, appDescription, jdkVersion } = this
109
- DEBUG?.({ env })
109
+ TRACE?.({ env })
110
110
 
111
111
  const _inProd = plugin => require(`./template/${plugin}`).hasInProduction(env)
112
112
  const _ui5 = () => exists('package.json') && !!JSON.parse(fs.readFileSync(path.resolve(cds.root, 'package.json'))).sapux
@@ -187,7 +187,6 @@ module.exports = new class ProjectReader {
187
187
  })
188
188
  })()
189
189
 
190
- DEBUG?.({ project })
191
190
  return project
192
191
  }
193
192
  }
@@ -0,0 +1,7 @@
1
+ {
2
+ "cds": {
3
+ "requires": {
4
+ "audit-log": true
5
+ }
6
+ }
7
+ }
@@ -36,7 +36,7 @@ module.exports = class AuditLoggingTemplate extends require('../../plugin') {
36
36
  const { hasMta, hasHelm, hasMultitenancy, srvPath } = project
37
37
  const { plan = 'premium' } = cds.cli.options
38
38
  project.servicePlan = plan
39
-
39
+
40
40
  if (hasMultitenancy) {
41
41
  await merge(__dirname, 'files/package.sidecar.json').into('mtx/sidecar/package.json')
42
42
  }
@@ -12,6 +12,10 @@ module.exports = class CfManifestTemplate extends require('../../plugin') {
12
12
  return 'Cloud Foundry deployment using manifest files'
13
13
  }
14
14
 
15
+ static hasInProduction() {
16
+ return exists(MANIFEST_FILE)
17
+ }
18
+
15
19
  async canRun() {
16
20
  if (cds.cli.options.force) {
17
21
  return true
@@ -15,7 +15,7 @@ const MAX_ASSOC_DEPTH = 5
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 } = options
18
+ let { referenceData, contentType } = options
19
19
 
20
20
  // if no entity restrictions are given, use all entities
21
21
  if (!data) data = csn.all('entity').reduce((all, e) => { all[e.name] = []; return all }, {})
@@ -63,6 +63,14 @@ const asJson = module.exports = async (data, csn, numRecords=1, options={}) => {
63
63
  // remove records w/ duplicate keys. This can happen if the key is an enum (and we have less enum values than requested records).
64
64
  data[name] = uniqRecords(data[name], entity)
65
65
  }
66
+ for (let [key, d] of Object.entries(data)) {
67
+ if (contentType === 'json' && d.deleteReference && !d.keepReferece) {
68
+ delete data[key]
69
+ }
70
+ delete d.keepReferece
71
+ delete d.deleteReference
72
+ uniqRecords(d, csn.definitions[key])
73
+ }
66
74
  return data
67
75
  }
68
76
 
@@ -222,17 +230,16 @@ function dataForField(element, entity, csn, record, data, parentEntity, depth, r
222
230
  }
223
231
  case 'cds.Composition': {
224
232
  const target = csn.definitions[element.target]
225
- if (!target)
226
- return
227
- delete data[target.name] // as data for the target is created inline below, no need to have it separately
228
- if (target === parentEntity || depth > MAX_ASSOC_DEPTH)
229
- return
230
-
231
- const targetRecords = []
233
+ if (!target || entity.name === target.name || depth > MAX_ASSOC_DEPTH) return
234
+
235
+ let targetRecords = [] // in findKeysInOnCondition target records are linked to the entity, we don't want to overwrite any existing data
236
+ if (data[target.name]) data[target.name].deleteReference = true // for json composites references are nested and will be deleted when writing to file
237
+
232
238
  const targetRecordCount = element.is2many ? 2 : 1 // TODO: make this configurable
233
239
  const targetRecordID = randomNumber()
234
240
  for (let i = 0; i < targetRecordCount; i++) {
235
- targetRecords.push(fillRecord({}, targetRecordID+i, target, csn, data, entity, depth+1, referenceData))
241
+ const newRecord = fillRecord({}, targetRecordID+i, target, csn, data, entity, depth+1, referenceData)
242
+ if (newRecord) targetRecords.push(newRecord)
236
243
  }
237
244
 
238
245
  const [ownKey, ownKeyStructured, targetKey] = findKeysInOnCondition(element, target)
@@ -268,11 +275,14 @@ function dataForField(element, entity, csn, record, data, parentEntity, depth, r
268
275
  return
269
276
 
270
277
  if (targetRecords.length > 0) { // use record generated by following asociation
271
- targetRecord = targetRecords[0]
278
+ targetRecord = targetRecords.at(-1) // make sure the localized record references the newly generated texts
272
279
  } else {
273
280
  targetRecord = fillRecord({}, randomNumber(), target, csn, data, entity, depth+1, referenceData)
274
- targetRecords.push(targetRecord)
275
- uniqRecords(targetRecords, target)
281
+ if (targetRecord) {
282
+ targetRecords.push(targetRecord)
283
+ uniqRecords(targetRecords, target)
284
+ }
285
+ if (parentEntity && (parentEntity.name != entity.name)) data[target.name].keepReferece = true
276
286
  }
277
287
  }
278
288
 
@@ -77,7 +77,7 @@ module.exports = class DataTemplate extends require('../../plugin') {
77
77
  .filter (e => queries || !e.query) // remove all projection-like entities unless specified differently
78
78
  .reduce((all, e) => { all[e.name] = []; return all }, {})
79
79
 
80
- await asJson(data, csn, records)
80
+ await asJson(data, csn, records, {contentType})
81
81
 
82
82
  if (contentType === 'csv') {
83
83
  const headerOnly = !cds.cli.options['records'] // compat to old behavior which only creates headers
@@ -1,7 +1,9 @@
1
1
  const cds = require('../../..')
2
2
  const { rimraf, exists, readFileSync, yaml } = cds.utils
3
3
  const { join } = require('path')
4
- const { merge, readProject } = cds.add ?? {}
4
+
5
+ const { readProject } = require('../../projectReader')
6
+ const { merge } = require('../../merge')
5
7
 
6
8
  module.exports = class HelmUnifiedRuntimeTemplate extends require('../helm') {
7
9
 
@@ -149,6 +149,7 @@ module.exports = class HttpTemplate extends require('../../plugin') {
149
149
  }
150
150
  varsDefined = true
151
151
  const headers = auth ? `Content-Type: application/json\n{{auth}}` : `Content-Type: application/json`
152
+ const headerAuth = auth ? '{{auth}}' : ''
152
153
 
153
154
  service.urlPaths.forEach((urlPath) => {
154
155
  if (service.urlPaths.length > 1) {
@@ -156,36 +157,41 @@ module.exports = class HttpTemplate extends require('../../plugin') {
156
157
  }
157
158
 
158
159
  service.entities.forEach((e) => {
159
- const entityNameSimple = e.name.slice(e.name.lastIndexOf('.') + 1)
160
+ const entityNameSimple = e.name.slice(serviceName.length + 1)
161
+ const nameSlug = slug(entityNameSimple)
160
162
  const url = `{{server}}/${urlPath}${entityNameSimple}`
161
- const requestTitle = `\n### ${e.name}`
162
- requests.push(`${requestTitle}\nGET ${url}\n${headers}\n`)
163
+ const requestTitle = `\n### ${entityNameSimple}`
164
+ requests.push(`${requestTitle}\n# @name ${nameSlug}_GET\nGET ${url}\n${headerAuth}\n`)
163
165
  if (e.readOnly) return
164
166
 
165
167
  const entityData = payload[`${e.name}`] ? payload[`${e.name}`][0] : null
166
168
  if (!entityData) return
167
- const postBody = JSON.stringify(entityData, null, 2)
168
-
169
- let id
170
- const compositeKey = Object.keys(e.keys).length > 1
171
- if (compositeKey) {
172
- id = e.keys.map(k => k.type === 'cds.String' ? `${k.name}='${entityData[k.name]}'` : `${k.name}=${entityData[k.name]}`).join(',')
173
- } else {
174
- id = entityData[Object.keys(e.keys)[0]]
175
- }
176
169
 
177
170
  if (e.isDraft) {
178
- generateDraftRequests(requests, requestTitle, url, headers, postBody, id)
179
- } else {
171
+ const key = Object.keys(e.keys)[0] // draft keys are always single
172
+ delete entityData[key] // draft keys should always by auto generated (either UUID or through custom logic), so remove them from the payload
173
+ const postBody = JSON.stringify(entityData, null, 2)
174
+ generateDraftRequests(requests, requestTitle, url, headers, headerAuth, postBody, key)
175
+ }
176
+ else {
177
+ const postBody = JSON.stringify(entityData, null, 2)
178
+ let id
179
+ const compositeKey = Object.keys(e.keys).length > 1
180
+ if (compositeKey) {
181
+ id = e.keys.map(k => k.type === 'cds.String' ? `${k.name}='${entityData[k.name]}'` : `${k.name}=${entityData[k.name]}`).join(',')
182
+ } else {
183
+ id = entityData[Object.keys(e.keys)[0]]
184
+ }
180
185
  compositeKey ? id = `(${id})` : id = `/${id}`
181
- requests.push(`${requestTitle}\nPOST ${url}\n${headers}\n\n${postBody}\n`)
182
- requests.push(`${requestTitle}\nPATCH ${url}${id}\n${headers}\n\n${postBody}\n`)
183
- requests.push(`${requestTitle}\nDELETE ${url}${id}\n${headers}\n`)
186
+ requests.push(`${requestTitle}\n# @name ${nameSlug}_POST\nPOST ${url}\n${headers}\n\n${postBody}\n`)
187
+ requests.push(`${requestTitle}\n# @name ${nameSlug}_PATCH\nPATCH ${url}${id}\n${headers}\n\n${postBody}\n`)
188
+ requests.push(`${requestTitle}\n# @name ${nameSlug}_DELETE\nDELETE ${url}${id}\n${headers}\n`)
184
189
  }
185
190
  })
186
191
 
187
192
  service.actions.forEach((a) => {
188
- const url = `{{server}}/${urlPath}${a.name.split(".")[1]}`
193
+ const simpleNameAction = a.name.slice(serviceName.length + 1)
194
+ const url = `{{server}}/${urlPath}${simpleNameAction}`
189
195
 
190
196
  let actionPayload = {}
191
197
  if (a.params) {
@@ -197,20 +203,25 @@ module.exports = class HttpTemplate extends require('../../plugin') {
197
203
  : null
198
204
  }
199
205
  }
200
- requests.push(`\n### ${a.name}\nPOST ${url}\n${headers}\n\n${JSON.stringify(actionPayload, null, 2)}\n`)
206
+ requests.push(`\n### ${simpleNameAction}\n# @name ${simpleNameAction}_POST\nPOST ${url}\n${headers}\n\n${JSON.stringify(actionPayload, null, 2)}\n`)
201
207
  })
202
208
 
203
209
  })
204
210
  this.#write(requests, serviceName)
205
211
  }
206
212
 
207
- function generateDraftRequests(requests, requestTitle, url, headers, postBody, id) {
208
- requests.push(`${requestTitle} Drafts \nGET ${url}?$filter=(IsActiveEntity eq false)\n${headers}\n`) // unencoded URL for better readability (client can handle this)
209
- requests.push(`${requestTitle} Drafts \nPOST ${url}\n${headers}\n\n${postBody}\n`)
210
- requests.push(`\n### Please paste the server-generated draft id here e.g. ID=101 or ID=101,secodKey='stringValue' \n@draftID = ${id}\n`)
211
- requests.push(`${requestTitle} Patch Draft \nPATCH ${url}({{draftID}},IsActiveEntity=false)\n${headers}\n\n${postBody}\n`)
212
- requests.push(`${requestTitle} Prepare Draft \nPOST ${url}({{draftID}},IsActiveEntity=false)/AdminService.draftPrepare\n${headers}\n\n{}\n`)
213
- requests.push(`${requestTitle} Activate Draft \nPOST ${url}({{draftID}},IsActiveEntity=false)/AdminService.draftActivate\n${headers}\n\n{}\n`)
213
+ function generateDraftRequests(requests, title, url, headers, headerAuth, postBody, key) {
214
+ const nameDraftsCreate = slug(title+'_Draft_POST')
215
+ requests.push(`${title} Drafts GET\n# @name ${slug(title+'_Drafts_GET')}\nGET ${url}?$filter=(IsActiveEntity eq false)\n${headerAuth}\n`) // unencoded URL for better readability (client can handle this)
216
+ requests.push(`${title} Draft POST\n# @name ${nameDraftsCreate}\nPOST ${url}\n${headers}\n\n${postBody}\n`)
217
+ requests.push(`\n### Result from POST request above\n@draftID = {{${nameDraftsCreate}.response.body.$.${key}}}\n`)
218
+ requests.push(`${title} Draft PATCH\n# @name ${slug(title+'_Draft_Patch')}\nPATCH ${url}(${key}={{draftID}},IsActiveEntity=false)\n${headers}\n\n${postBody}\n`)
219
+ requests.push(`${title} Draft Prepare\n# @name ${slug(title+'_Draft_Prepare')}\nPOST ${url}(${key}={{draftID}},IsActiveEntity=false)/AdminService.draftPrepare\n${headers}\n\n{}\n`)
220
+ requests.push(`${title} Draft Activate\n# @name ${slug(title+'_Draft_Activate')}\nPOST ${url}(${key}={{draftID}},IsActiveEntity=false)/AdminService.draftActivate\n${headers}\n\n{}\n`)
221
+ }
222
+
223
+ function slug(str) {
224
+ return str.trim().replace(/[#\\. ]/g, '')
214
225
  }
215
226
  }
216
227
 
@@ -248,5 +259,3 @@ module.exports = class HttpTemplate extends require('../../plugin') {
248
259
  return `${outputDir}/${serviceName}.http`
249
260
  }
250
261
  }
251
-
252
-
@@ -8,7 +8,7 @@ const { copyRenderedJSON } = require('../../../util/fs')
8
8
  const {
9
9
  srv4, // Server
10
10
  mtxSidecar4, // Additional Modules
11
- saasRegistry, serviceManager, xsuaa, // BTP Services
11
+ saasRegistry, // BTP Services
12
12
  srvApi4, providedMtxApiSidecar4, requiredMtxSidecarApi4 // APIs
13
13
  } = require('../../registries/mta')
14
14
 
@@ -47,13 +47,9 @@ module.exports = class MultitenancyTemplate extends require('../../plugin') {
47
47
  const mtxSidecar = mtxSidecar4(isJava ? 'mtx/sidecar' : 'gen/mtx/sidecar')
48
48
  const modules = [srv, mtxSidecar]
49
49
  const services = [saasRegistry]
50
- if (hasXsuaa) services.push(xsuaa)
51
50
  const apis = [srvApi4(srv), providedMtxApiSidecar4(mtxSidecar)]
52
51
  if (isJava) apis.push(requiredMtxSidecarApi4(srv))
53
52
  const additions = [...modules, ...services, ...apis]
54
-
55
- const overwrites = hasXsuaa ? [{ item: [xsuaa, 'parameters.config.tenant-mode'], withValue: 'shared' }] : []
56
-
57
53
  const relationships = [{
58
54
  insert: [saasRegistry, 'name'],
59
55
  into: [isJava ? srv : mtxSidecar, 'requires', 'name']
@@ -64,34 +60,16 @@ module.exports = class MultitenancyTemplate extends require('../../plugin') {
64
60
  into: [mtxSidecar, 'build-parameters.requires', 'name']
65
61
  })
66
62
  }
67
- if (hasXsuaa) {
68
- relationships.push({
69
- insert: [xsuaa, 'name'],
70
- into: [mtxSidecar, 'requires', 'name']
71
- })
72
- }
73
- if (hasHana) {
74
- relationships.push({
75
- insert: [serviceManager, 'name'],
76
- into: [mtxSidecar, 'requires', 'name'],
77
- })
78
- }
79
63
 
80
- const templateMtaPath = join(__dirname, 'files', 'mta.yaml.hbs')
81
- await merge(templateMtaPath).into('mta.yaml', { with: project, additions, overwrites, relationships })
64
+ const templateMtaPath = join(__dirname, 'files/mta.yaml.hbs')
65
+ await merge(templateMtaPath).into('mta.yaml', { with: project, additions, relationships })
82
66
  }
83
67
 
84
68
  if (hasHelm || hasHelmUnifiedRuntime) {
85
- const overwrites = hasXsuaa ? [{
86
- item: [{
87
- in: 'xsuaa',
88
- where: { serviceOfferingName: 'xsuaa' }
89
- }, 'parameters.tenant-mode'], withValue: 'shared'
90
- }] : []
91
69
 
92
- await merge(__dirname, 'files/values.yaml.hbs').into('chart/values.yaml', { with: project, overwrites })
70
+ await merge(__dirname, 'files/values.yaml.hbs').into('chart/values.yaml', { with: project })
93
71
 
94
- const domain = hasApprouter ? 'svc.cluster.local:8080' : '{{ .Values.global.domain }}'
72
+ const domain = hasApprouter ? 'svc.cluster.local:8080' : '{{ .Values.global.domain }}'
95
73
  const applicationWorkload = hasApprouter ? 'approuter' : 'srv'
96
74
  const serviceCommunicatingWithSaasReg = hasApprouter ? 'approuter' : isNodejs ? 'sidecar' : 'srv';
97
75
  await merge({
@@ -102,7 +80,7 @@ module.exports = class MultitenancyTemplate extends require('../../plugin') {
102
80
  appUrls: {
103
81
  getDependencies: `https://{{ .Release.Name }}-${serviceCommunicatingWithSaasReg}-{{ .Release.Namespace }}.{{ .Values.global.domain }}${isNodejs ? '/-/cds/saas-provisioning' : '/mt/v1.0/subscriptions'}/dependencies`,
104
82
  onSubscription: `https://{{ .Release.Name }}-${serviceCommunicatingWithSaasReg}-{{ .Release.Namespace }}.{{ .Values.global.domain }}${isNodejs ? '/-/cds/saas-provisioning/tenant/{tenantId}' : '/mt/v1.0/subscriptions/tenants/{tenantId}'}`,
105
- }
83
+ }
106
84
  }
107
85
  },
108
86
  ...(isNodejs) && { sidecar: {
@@ -110,12 +88,12 @@ module.exports = class MultitenancyTemplate extends require('../../plugin') {
110
88
  SUBSCRIPTION_URL: `https://\${tenant_subdomain}-{{ .Release.Name }}-${applicationWorkload}-{{ .Release.Namespace }}.{{ .Values.global.domain }}`
111
89
  }
112
90
  }},
113
- ...(isJava) && { srv: {
91
+ ...(isJava) && { srv: {
114
92
  env: {
115
93
  CDS_MULTITENANCY_APPUI_URL: `https://{{ .Release.Name }}-${applicationWorkload}-{{ .Release.Namespace }}.{{ .Values.global.domain }}`,
116
94
  CDS_MULTITENANCY_SIDECAR_URL: `http${hasApprouter ? '' : 's'}://{{ .Release.Name }}-sidecar${hasApprouter ? '.' : '-'}{{ .Release.Namespace }}.${domain}`
117
- }
118
- }}
95
+ }
96
+ }}
119
97
  }).into('chart/values.yaml',{ forceOverwrite: true })
120
98
  }
121
99
 
@@ -55,7 +55,6 @@
55
55
  "": {
56
56
  "dataSource": "AdminService",
57
57
  "settings": {
58
- "synchronizationMode": "None",
59
58
  "operationMode": "Server",
60
59
  "autoExpandSelect": true,
61
60
  "earlyRequests": true,
@@ -71,7 +71,6 @@
71
71
  "": {
72
72
  "dataSource": "CatalogService",
73
73
  "settings": {
74
- "synchronizationMode": "None",
75
74
  "operationMode": "Server",
76
75
  "autoExpandSelect": true,
77
76
  "earlyRequests": true,