@sap/cds-dk 10.0.5 → 10.0.7

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 (58) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/bin/export.js +1 -1
  3. package/bin/version.js +7 -3
  4. package/lib/build/plugins/hana/index.js +19 -2
  5. package/lib/import/asyncapi/parserContext.js +136 -54
  6. package/lib/init/projectReader.js +4 -2
  7. package/lib/init/template/ams/files/package.json.hbs +1 -1
  8. package/lib/init/template/nodejs/index.js +4 -2
  9. package/lib/init/template/test/files/test/test.xs.hbs +3 -2
  10. package/lib/init/template/xsuaa/index.js +11 -7
  11. package/node_modules/@cap-js/openapi/lib/compile/csdl.js +3 -3
  12. package/node_modules/@cap-js/openapi/lib/compile/csdl2openapi.js +111 -103
  13. package/node_modules/@cap-js/openapi/lib/compile/diagram.js +4 -4
  14. package/node_modules/@cap-js/openapi/lib/compile/types.d.ts +3 -1
  15. package/node_modules/@cap-js/openapi/package.json +3 -3
  16. package/node_modules/@sap/cds/CHANGELOG.md +16 -0
  17. package/node_modules/@sap/cds/lib/compile/for/nodejs.js +1 -0
  18. package/node_modules/@sap/cds/lib/env/defaults.js +1 -0
  19. package/node_modules/@sap/cds/lib/plugins.js +4 -2
  20. package/node_modules/@sap/cds/lib/srv/srv-handlers.js +1 -1
  21. package/node_modules/@sap/cds/libx/_runtime/common/Service.js +1 -1
  22. package/node_modules/@sap/cds/libx/_runtime/messaging/common-utils/authorizedRequest.js +3 -2
  23. package/node_modules/@sap/cds/libx/_runtime/remote/Service.js +1 -1
  24. package/node_modules/@sap/cds/package.json +1 -1
  25. package/node_modules/@sap/cds-compiler/CHANGELOG.md +12 -0
  26. package/node_modules/@sap/cds-compiler/lib/compiler/populate.js +1 -0
  27. package/node_modules/@sap/cds-compiler/lib/compiler/resolve.js +1 -1
  28. package/node_modules/@sap/cds-compiler/lib/model/csnUtils.js +13 -8
  29. package/node_modules/@sap/cds-compiler/package.json +1 -1
  30. package/node_modules/@sap/cds-mtxs/CHANGELOG.md +36 -0
  31. package/node_modules/@sap/cds-mtxs/bin/cds-mtx-migrate.js +32 -48
  32. package/node_modules/@sap/cds-mtxs/bin/cds-mtx.js +1 -0
  33. package/node_modules/@sap/cds-mtxs/cds-plugin.js +18 -0
  34. package/node_modules/@sap/cds-mtxs/lib/migration/migration.js +3 -408
  35. package/node_modules/@sap/cds-mtxs/package.json +1 -1
  36. package/node_modules/@sap/cds-mtxs/srv/cf/abstract-provisioning-service.js +5 -6
  37. package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/annotations.js +5 -2
  38. package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/index.js +6 -5
  39. package/node_modules/@sap/cds-mtxs/srv/extensibility-service.cds +0 -11
  40. package/node_modules/@sap/cds-mtxs/srv/extensibility-service.js +12 -22
  41. package/node_modules/@sap/cds-mtxs/srv/jobs-service.js +30 -24
  42. package/node_modules/@sap/cds-mtxs/srv/model-provider.js +2 -2
  43. package/node_modules/@sap/cds-mtxs/srv/plugins/hana/ctnr-mgr-base.js +5 -2
  44. package/node_modules/@sap/cds-mtxs/srv/plugins/hana/hdi.js +5 -3
  45. package/node_modules/@sap/cds-mtxs/srv/plugins/hana/srv-mgr.js +3 -2
  46. package/node_modules/@sap/cds-mtxs/srv/plugins/hana/tms.js +17 -0
  47. package/node_modules/@sap/cds-mtxs/srv/plugins/hana.js +14 -13
  48. package/node_modules/media-typer/README.md +15 -14
  49. package/node_modules/media-typer/index.js +0 -8
  50. package/node_modules/media-typer/package.json +16 -12
  51. package/node_modules/sax/lib/sax.js +27 -9
  52. package/node_modules/sax/package.json +1 -1
  53. package/node_modules/ws/package.json +1 -1
  54. package/package.json +4 -4
  55. package/lib/init/template/nodejs/files/.gitignore.hbs +0 -34
  56. package/node_modules/@sap/cds-mtxs/lib/migration/extension-project-builder.js +0 -129
  57. package/node_modules/@sap/cds-mtxs/lib/migration/extension-project.js +0 -89
  58. package/node_modules/@sap/cds-mtxs/lib/migration/mtx-adapter.js +0 -230
package/CHANGELOG.md CHANGED
@@ -6,10 +6,38 @@ This project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  The format is based on [Keep a Changelog](https://keepachangelog.com/).
8
8
 
9
+ ## Version 10.0.7 - 2026-08-07
10
+
11
+ ### Fixed
12
+
13
+ - Bundle `@sap/cds-mtxs` 4 again, instead of 3.
14
+ - `cds import --from asyncapi` now correctly imports types referenced via `$ref` in AsyncAPI schemas, including `allOf` composition, and no longer loses types from earlier events when a service has multiple events.
15
+ - `cds add html5-repo`, `cds add workzone`, and `cds add portal` no longer emit an HTML5 application content deployer module into `mta.yaml` when the project has no UI apps, avoiding the `could not find the ".../gen/app" path` build failure.
16
+ - `cds init --nodejs` creates the ESLint and VS Code config files again
17
+
18
+ ### Changed
19
+
20
+ - `cds init --nodejs` creates a cleaned-up .gitignore file
21
+
22
+ ## Version 10.0.6 - 2026-07-24
23
+
24
+ ### Added
25
+
26
+ - `cds add xsuaa` now generates namespace-suffixed XSUAA `role-collections` for Kyma.
27
+
28
+ ### Fixed
29
+
30
+ - `cds add http` no longer throws `MULTIPLE_DOCS` when `application.yaml` uses `---` separators (Spring Boot multi-profile format).
31
+ - `cds export --data` now reads seed data via in-memory SQLite instead of the configured db driver, so it no longer fails in projects with `requires.db: hana` (e.g. CAP Java).
32
+ - `cds init --java --add mta` now writes `path: srv` for the Java `srv` module in `mta.yaml` instead of `gen/srv`, so `mbt build` no longer fails with `the "gen/srv" path of the "<app>-srv" module does not exist`.
33
+ - `cds version` no longer reports dependency problems if no dependencies are installed at all. It prints an info message instead.
34
+ - `cds add test` creates tests that are green by default. Sample data is provided as commented code.
35
+
9
36
  ## Version 10.0.5 - 2026-07-16
10
37
 
11
38
  ### Fixed
12
39
 
40
+ - `cds build --for hana` now declares the plugin for native HANA artifacts placed in the build output (e.g. a `.hdbprojectionview` from `db/src`) in the generated `.hdiconfig`.
13
41
  - `cds add typer` now generates a `jsconfig.json` file that works with VS Code.
14
42
  - `cds debug <app> --force` on Kyma now preserves existing `NODE_OPTIONS` flags (e.g. `--max-old-space-size`) instead of overwriting them when enabling the inspector.
15
43
  - `cds add sample` now selects `stock` in the Books list and object pages, so the overstock discount shows up in the UI.
package/bin/export.js CHANGED
@@ -200,7 +200,7 @@ class Export extends Minify {
200
200
  }
201
201
 
202
202
  async export_data (model) {
203
- const db = cds.db = await cds.deploy ('*',{silent:true})
203
+ const db = cds.db = await cds.deploy ('*',{silent:true}).to ('sqlite::memory:')
204
204
  const out = path.resolve (this.options.to, 'data')
205
205
  await fs.promises.rm (out, { recursive: true, force: true })
206
206
  await mkdirp (this.options.to, 'data')
package/bin/version.js CHANGED
@@ -1,3 +1,4 @@
1
+ const cds = require('../lib')
1
2
 
2
3
  module.exports = exports = async function cds_version (pattern, options) {
3
4
  console.log()
@@ -9,7 +10,12 @@ module.exports = exports = async function cds_version (pattern, options) {
9
10
  print_versions (all.flat(),o)
10
11
  if (!o.json) {
11
12
  if (o.info) console.log ('\n','> Project:', project_info (o))
12
- if (o._elsp) console.log ('\n', `${RED}ELSPROBLEMS${RESET} reported by ${BOLD}npm ls${RESET}. Run ${BOLD}npm install${RESET} to fix dependency problems, then rerun ${BOLD}cds version${RESET}.`)
13
+ if (o._elsp) {
14
+ if (!cds.utils.fs.existsSync(join(o.root, 'node_modules')))
15
+ console.log (`\nNo dependencies installed. Run ${BOLD}npm install${RESET}, then rerun ${BOLD}cds version${RESET}.`)
16
+ else
17
+ console.log (`\n${RED}ELSPROBLEMS${RESET} reported by ${BOLD}npm ls${RESET}. Run ${BOLD}npm install${RESET} to fix dependency problems, then rerun ${BOLD}cds version${RESET}.`)
18
+ }
13
19
  }
14
20
  console.log()
15
21
  }
@@ -49,8 +55,6 @@ exports.help = `
49
55
  repository URL at the end as a markdown link.
50
56
  `
51
57
 
52
-
53
- const cds = require('../lib')
54
58
  const $ = cds.utils.sh || sh_polyfill
55
59
  const child_process = require('node:child_process')
56
60
  const home = require('os').homedir()
@@ -68,7 +68,6 @@ module.exports = class HanaBuildPlugin extends require('../internal') {
68
68
  }
69
69
 
70
70
  // create additional stuff in dest
71
- await this._writeHdiConfig(hdiPlugins)
72
71
  await this._writeHdiNamespace()
73
72
  await this._writeUndeployJson()
74
73
 
@@ -100,6 +99,8 @@ module.exports = class HanaBuildPlugin extends require('../internal') {
100
99
  )
101
100
  }
102
101
 
102
+ await this._writeHdiConfig(hdiPlugins)
103
+
103
104
  // relocate .hdbsynonymconfig files containing *.configure properties to cfg/ folder
104
105
  // HDI's template mechanism for configuration files only works from the cfg/ folder
105
106
  // For inplace builds (Java), restrict the scan to compileDest only to avoid moving user's
@@ -433,7 +434,8 @@ module.exports = class HanaBuildPlugin extends require('../internal') {
433
434
  async _writeHdiConfig(compileHdiPlugins) {
434
435
  const DEFAULT_HDI_PLUGINS = [FILE_EXT_CSV, FILE_EXT_HDBTABLEDATA, FILE_EXT_HDBTABLE, ".hdbview", ".hdbindex", ".hdbconstraint", ".hdbcalculationview"]
435
436
  const undeployHdiPlugins = await this._readTypesFromUndeployJson() // see CAP issue #6222
436
- const hdiPlugins = new Set([...DEFAULT_HDI_PLUGINS, ...undeployHdiPlugins, ...compileHdiPlugins])
437
+ const destHdiPlugins = await this._readTypesFromCompileDest()
438
+ const hdiPlugins = new Set([...DEFAULT_HDI_PLUGINS, ...undeployHdiPlugins, ...compileHdiPlugins, ...destHdiPlugins])
437
439
  let hdiConfigPath
438
440
 
439
441
  // do not create the .hdiconfig file in 'gen/db' root folder for now
@@ -543,6 +545,21 @@ module.exports = class HanaBuildPlugin extends require('../internal') {
543
545
  return hdiPlugins
544
546
  }
545
547
 
548
+ async _readTypesFromCompileDest() {
549
+ const hdiPlugins = new Set()
550
+ const dir = path.join(this.task.dest, 'src')
551
+ if (!exists(dir)) return hdiPlugins
552
+
553
+ const template = await _readTemplateAsJson('.hdiconfig-all')
554
+ const knownSuffixes = new Set(Object.keys(template['file_suffixes']).map(key => '.' + key))
555
+ const files = await find(dir, { filter: (entry) => !isdir(entry) })
556
+ files.forEach(file => {
557
+ const suffix = path.extname(file)
558
+ if (knownSuffixes.has(suffix)) hdiPlugins.add(suffix)
559
+ })
560
+ return hdiPlugins
561
+ }
562
+
546
563
  async _validateMigrationTableFiles() {
547
564
  const dbSrcDir = path.join(this.task.src, "src")
548
565
  const migrationTableFiles = await find(dbSrcDir, { filter: (res) => {
@@ -1,18 +1,33 @@
1
1
  const { getRegExpGroups } = require('./utils');
2
2
  const messages = require("../message").getMessages();
3
3
 
4
+ /**
5
+ * @typedef {{ type?: string|string[], format?: string, properties?: Record<string, AsyncApiSchema>, items?: AsyncApiSchema, allOf?: AsyncApiSchema[], '$ref'?: string, required?: string[], additionalProperties?: boolean|AsyncApiSchema, enum?: Array<*>, default?: *, nullable?: boolean, [key: string]: * }} AsyncApiSchema
6
+ * @typedef {{ components: { messages: Record<string, AsyncApiMessage>, schemas: Record<string, AsyncApiSchema> }, info?: object, [key: string]: * }} AsyncApiDocument
7
+ * @typedef {{ name: string, payload?: AsyncApiSchema, [key: string]: * }} AsyncApiMessage
8
+ * @typedef {{ type?: string|{'ref':string[]}, kind?: string, elements?: Record<string, CsnElement>, items?: CsnElement, '@cds.external'?: string, '@mandatory'?: boolean, precision?: number, scale?: number, default?: *, enum?: object, [key: string]: * }} CsnElement
9
+ * @typedef {Record<string, *>} Annotations
10
+ * @typedef {Record<string, CsnElement | null>} CsnTypeMap
11
+ */
12
+
4
13
  module.exports = class ParserContext {
5
14
  /**
6
- * @constructor
15
+ * @param {AsyncApiDocument} content
7
16
  */
8
17
  constructor(content) {
9
18
  this.services = this.getServicesFromContent(content);
10
19
  }
11
20
 
21
+ /**
22
+ * @param {string} eventName
23
+ */
12
24
  getTopicName(eventName) {
13
25
  return eventName.startsWith('ce/') ? eventName.substring(3) : eventName;
14
26
  }
15
27
 
28
+ /**
29
+ * @param {string} messageRefString
30
+ */
16
31
  getEventName(messageRefString) {
17
32
  const lastSlashIndex = messageRefString.lastIndexOf('/');
18
33
  return lastSlashIndex == -1 ? '' : messageRefString.substring(lastSlashIndex + 1);
@@ -20,9 +35,10 @@ module.exports = class ParserContext {
20
35
 
21
36
  /**
22
37
  * Returns services from the asyncapi json object along with the events and annotations.
23
- * @returns an object with all the services.
38
+ * @param {AsyncApiDocument} content - root AsyncAPI document
24
39
  */
25
40
  getServicesFromContent(content) {
41
+ /** @type {Record<string, *>} */
26
42
  const services = {};
27
43
  const serviceAnnotations = this.getServiceAnnotations(content);
28
44
 
@@ -49,15 +65,17 @@ module.exports = class ParserContext {
49
65
 
50
66
  // add topic annotation
51
67
  eventAnnotations['@topic'] = this.getTopicName(eventName);
68
+ /** @type {Record<string, CsnElement>} */
52
69
  let properties = {};
70
+ /** @type {CsnTypeMap} */
53
71
  let types = {};
54
72
 
55
73
  if (value.payload) {
56
74
  // gets the schema being referred to in the event
57
75
  const eventSchema = this.resolveRef(content, value.payload);
58
- [properties, types] = this.getPropertiesAndTypes(event, eventSchema);
76
+ [properties, types] = this.getPropertiesAndTypes(event, eventSchema, content);
59
77
  }
60
- services[service].types = types;
78
+ services[service].types = { ...services[service].types, ...types };
61
79
  services[service].events[event] = { 'kind': 'event', '@cds.external': 'true', ...eventAnnotations, elements: properties };
62
80
  });
63
81
 
@@ -66,7 +84,7 @@ module.exports = class ParserContext {
66
84
 
67
85
  /**
68
86
  * Returns annotations of a particular service.
69
- * @returns an object with all the annotations as key value pairs.
87
+ * @param {AsyncApiDocument} content - root AsyncAPI document
70
88
  */
71
89
  getServiceAnnotations(content) {
72
90
  const serviceAnnotationsMapping = {
@@ -84,9 +102,10 @@ module.exports = class ParserContext {
84
102
 
85
103
  if (content.info) {
86
104
  for (const [key, value] of Object.entries(infoAnnotationsMapping)) {
87
- if (content.info[key] && !(content.info[key] instanceof Object)) {
105
+ /** @type {Record<string, *>} */ const info = content.info;
106
+ if (info[key] && !(info[key] instanceof Object)) {
88
107
  // info values will always be in string format
89
- annotations['@AsyncAPI.' + value] = content.info[key];
108
+ annotations['@AsyncAPI.' + value] = info[key];
90
109
  }
91
110
  }
92
111
  }
@@ -95,8 +114,8 @@ module.exports = class ParserContext {
95
114
  }
96
115
 
97
116
  /**
98
- * Returns annotations of a particular event. Here the parameter content is event content.
99
- * @returns an object with all the annotations as key value pairs.
117
+ * Returns annotations of a particular event.
118
+ * @param {AsyncApiMessage} content - message object from components.messages
100
119
  */
101
120
  getEventAnnotations(content) {
102
121
  const eventLevelAnnotationsMapping = {
@@ -114,9 +133,8 @@ module.exports = class ParserContext {
114
133
  }
115
134
 
116
135
  /**
117
- * Returns validity of the annotation value. Here the parameter
118
- * content is the annotation name and value of the annotation.
119
- * @returns true/false depending on the annotation value.
136
+ * @param {string} annotation - annotation name (without @AsyncAPI. prefix)
137
+ * @param {*} value
120
138
  */
121
139
  isValidAnnotationValue(annotation, value) {
122
140
  const objectTypeAnnotations = ['StateInfo', 'EventSourceParams', 'EventCharacteristics', 'EventStateInfo'];
@@ -134,11 +152,12 @@ module.exports = class ParserContext {
134
152
  }
135
153
 
136
154
  /**
137
- * Returns annotations for an event/service. Here the parameter content
138
- * is event/service content along with the corresponding mapping.
139
- * @returns an object with the annotations as key value pairs.
155
+ * Returns annotations for an event/service.
156
+ * @param {object} content - event or service object
157
+ * @param {Record<string, string>} annotationsMapping - map of AsyncAPI extension key to CDS annotation suffix
140
158
  */
141
159
  getAnnotations(content, annotationsMapping) {
160
+ /** @type {Annotations} */
142
161
  const annotations = {};
143
162
 
144
163
  for (const [key, value] of Object.entries(content)) {
@@ -160,11 +179,33 @@ module.exports = class ParserContext {
160
179
  }
161
180
 
162
181
  /**
163
- * Returns the referred schema. Here the content is the asyncapi
164
- * document and payloadObject is the reference path
165
- * @returns an object with the referred schema.
182
+ * Merges allOf entries into a single schema object with combined properties.
183
+ * Resolves any $ref entries within the allOf array using the provided document.
184
+ * @param {AsyncApiSchema} schema - AsyncAPI schema object, possibly containing allOf
185
+ * @param {AsyncApiDocument} docContent - root AsyncAPI document, used to resolve $ref entries
186
+ */
187
+ flattenAllOf(schema, docContent) {
188
+ if (!schema.allOf) return schema;
189
+ /** @type {AsyncApiSchema} */
190
+ const merged = { type: 'object', properties: {} };
191
+ const required = [];
192
+ for (const entry of schema.allOf) {
193
+ const resolved = entry['$ref'] ? this.resolveRef(docContent, entry) : entry;
194
+ const flat = this.flattenAllOf(resolved, docContent);
195
+ Object.assign(merged.properties ?? {}, flat.properties || {});
196
+ if (flat.required) required.push(...flat.required);
197
+ }
198
+ if (required.length) merged.required = required;
199
+ return merged;
200
+ }
201
+
202
+ /**
203
+ * Returns the referred schema.
204
+ * @param {AsyncApiDocument} content - root AsyncAPI document
205
+ * @param {AsyncApiSchema | {'$ref': string}} payloadObject - either a $ref object or an inline schema
166
206
  */
167
207
  resolveRef(content, payloadObject) {
208
+ /** @type {AsyncApiSchema} */
168
209
  let schema = {};
169
210
 
170
211
  if (payloadObject['$ref']) {
@@ -190,40 +231,46 @@ module.exports = class ParserContext {
190
231
 
191
232
  /**
192
233
  * Returns the properties and types generated, related to an event.
193
- * @returns a properties object and a types object.
234
+ * @param {string} eventName
235
+ * @param {AsyncApiSchema} content - resolved AsyncAPI schema object for the event payload
236
+ * @param {AsyncApiDocument} docContent - root AsyncAPI document, used to resolve $ref entries
237
+ * @returns {[Record<string, CsnElement>, CsnTypeMap]}
194
238
  */
195
- getPropertiesAndTypes(eventName, content) {
239
+ getPropertiesAndTypes(eventName, content, docContent) {
240
+ /** @type {Record<string, CsnElement>} */
196
241
  const properties = {};
197
- let types = {};
242
+ /** @type {CsnTypeMap} */
243
+ const types = {};
198
244
 
199
245
  if (content.properties) {
200
246
  for (const [key, value] of Object.entries(content.properties)) {
201
- // will contain type objects generated for a property
202
- let referredTypes = {};
203
247
  let isRequired = false;
204
248
  if (content['required']?.includes(key)) isRequired = true;
205
- [properties[key], referredTypes] = this.parseProperty(eventName, key, value, isRequired);
206
- // collecting all the types
207
- types = { ...types, ...referredTypes };
249
+ properties[key] = this.parseProperty(eventName, key, value, isRequired, docContent, types);
208
250
  }
209
251
  }
210
252
  return [properties, types];
211
253
  }
212
254
 
213
255
  /**
214
- * Returns the processed objects for property and type.
215
- * @returns property object and type object.
256
+ * Returns the processed CSN element object for a property.
257
+ * @param {string} eventName
258
+ * @param {string} propertyName
259
+ * @param {AsyncApiSchema} propertyContent - AsyncAPI schema for this property
260
+ * @param {boolean} isRequired
261
+ * @param {AsyncApiDocument} docContent - root AsyncAPI document, used to resolve $ref entries
262
+ * @param {CsnTypeMap} types - accumulator for generated CDS type definitions
216
263
  */
217
- parseProperty(eventName, propertyName, propertyContent, isRequired) {
264
+ parseProperty(eventName, propertyName, propertyContent, isRequired, docContent, types) {
218
265
  let property = {};
219
266
  let propertyAttributes = {};
220
- let types = {};
221
267
 
222
268
  if (isRequired === true) {
223
269
  property['@mandatory'] = true;
224
270
  }
225
271
 
226
272
  let type = this.getTypeFromArray(propertyContent.type);
273
+ if (!type && propertyContent.allOf) type = 'object';
227
274
 
228
275
  // case where type is defined
229
276
  if (type && (type === 'object' || type === 'array')) {
@@ -231,48 +278,55 @@ module.exports = class ParserContext {
231
278
  propertyName = propertyName[0].toUpperCase() + propertyName.substring(1);
232
279
  let typeName = eventName + "." + propertyName;
233
280
  // generate object for the introduced type
234
- const elements = this.getTypeElements(propertyContent);
281
+ const elements = this.getTypeElements(propertyContent, docContent, types);
235
282
  types[typeName] = { 'kind': 'type', '@cds.external': 'true', ...elements };
236
283
  // referring to the generated type from the property
237
284
  propertyAttributes = { 'type': typeName };
238
285
  } else {
239
- propertyAttributes = this.getPropertyObject(propertyContent);
286
+ propertyAttributes = this.getPropertyObject(propertyContent, docContent, types);
240
287
  }
241
288
  property = { ...property, ...propertyAttributes };
242
289
 
243
- return [property, types];
290
+ return property;
244
291
  }
245
292
 
246
293
  /**
247
- * Returns generated type for the property.
248
- * @returns an object for a type.
294
+ * Returns the CSN structure for a complex (object/array) schema element.
295
+ * @param {AsyncApiSchema} propertyContent - AsyncAPI schema for this property
296
+ * @param {AsyncApiDocument} docContent - root AsyncAPI document, used to resolve $ref entries
297
+ * @param {CsnTypeMap} types - accumulator for generated CDS type definitions
298
+ * @returns {CsnElement}
249
299
  */
250
- getTypeElements(propertyContent) {
300
+ getTypeElements(propertyContent, docContent, types) {
301
+ /** @type {CsnElement} */
251
302
  let properties = {};
252
- let type = this.getTypeFromArray(propertyContent.type);
303
+ const schema = propertyContent.allOf ? this.flattenAllOf(propertyContent, docContent) : propertyContent;
304
+ let type = this.getTypeFromArray(schema.type);
253
305
 
254
306
  // for nested object
255
307
  if (type === 'object') {
256
- for (const [key, value] of Object.entries(propertyContent.properties)) {
257
- properties[key] = this.getTypeElements(value);
308
+ if (!schema.properties) return {};
309
+ for (const [key, value] of Object.entries(schema.properties)) {
310
+ properties[key] = this.getTypeElements(value, docContent, types);
258
311
  }
259
312
  }
260
313
  // for nested array
261
314
  else if (type === 'array') {
262
- properties.items = this.getTypeElements(propertyContent.items);
315
+ if (!schema.items || (!schema.items.type && !schema.items['$ref'] && !schema.items.allOf)) return {};
316
+ properties.items = this.getTypeElements(schema.items, docContent, types);
263
317
  return properties;
264
318
  }
265
319
  // base data type mapping
266
320
  else {
267
- return this.getPropertyObject(propertyContent);
321
+ return this.getPropertyObject(schema, docContent, types);
268
322
  }
269
323
 
270
324
  return { elements: properties };
271
325
  }
272
326
 
273
327
  /**
274
- * Returns type for the property from the AsyncAPI Document.
275
- * @returns the value of type.
328
+ * Unwraps nullable array type representations to a single type string.
329
+ * @param {string | string[] | undefined} typeValue
276
330
  */
277
331
  getTypeFromArray(typeValue) {
278
332
  let type;
@@ -288,15 +342,41 @@ module.exports = class ParserContext {
288
342
  }
289
343
 
290
344
  /**
291
- * Returns generated object for a single property.
292
- * @returns an object for a property.
345
+ * Returns the CSN element object for a scalar or $ref property.
346
+ * @param {AsyncApiSchema} propertyContent - AsyncAPI schema for this property
347
+ * @param {AsyncApiDocument} docContent - root AsyncAPI document, used to resolve $ref entries
348
+ * @param {CsnTypeMap} types - accumulator for generated CDS type definitions
293
349
  */
294
- getPropertyObject(propertyContent) {
350
+ getPropertyObject(propertyContent, docContent, types) {
351
+ /** @type {CsnElement} */
295
352
  const property = {};
296
353
  if ('$ref' in propertyContent) {
297
- const refPathParts = propertyContent['$ref'].split('/');
298
- const propertyName = refPathParts[refPathParts.length - 1];
299
- property['type'] = { 'ref': [refPathParts[3], propertyName] };
354
+ const refPathParts = (propertyContent['$ref'] ?? '').split('/');
355
+ const schemaName = refPathParts[3] ?? '';
356
+ const isWholeSchemaRef = () => refPathParts.length === 4 && refPathParts[1] === 'components' && refPathParts[2] === 'schemas';
357
+
358
+ if (isWholeSchemaRef()) {
359
+ // $ref to a whole top-level schema — resolve it and emit as a CDS type
360
+ if (!types[schemaName]) {
361
+ types[schemaName] = null; // placeholder to guard against circular refs
362
+ const resolvedSchema = this.resolveRef(docContent, propertyContent);
363
+ const flatSchema = resolvedSchema.allOf ? this.flattenAllOf(resolvedSchema, docContent) : resolvedSchema;
364
+ const elements = this.getTypeElements(flatSchema, docContent, types);
365
+ // only emit a named type if it has structure; otherwise fall back to LargeString
366
+ if (Object.keys(elements).length > 0) {
367
+ types[schemaName] = { 'kind': 'type', '@cds.external': 'true', ...elements };
368
+ } else {
369
+ delete types[schemaName];
370
+ property['type'] = 'cds.LargeString';
371
+ return property;
372
+ }
373
+ }
374
+ property['type'] = schemaName;
375
+ } else {
376
+ // $ref to a specific property within a schema (e.g. /properties/propName)
377
+ const propertyName = refPathParts[refPathParts.length - 1];
378
+ property['type'] = { 'ref': [schemaName, propertyName] };
379
+ }
300
380
  return property;
301
381
  }
302
382
  const inputType = this.getTypeFromArray(propertyContent.type);
@@ -322,24 +402,26 @@ module.exports = class ParserContext {
322
402
  }
323
403
 
324
404
  /**
325
- * Takes the enums from the AsyncAPI Document.
326
- * @returns the property object of enums.
405
+ * @param {Array<*>} enumArray
327
406
  */
328
407
  getEnumObject(enumArray) {
408
+ /** @type {Record<string, {}>} */
329
409
  const enumProperty = {};
330
410
 
331
411
  enumArray.forEach(val => {
332
- enumProperty[val] = {};
412
+ enumProperty[String(val)] = {};
333
413
  });
334
414
 
335
415
  return enumProperty;
336
416
  }
337
417
 
338
418
  /**
339
- * Takes the type and format from the document and returns the corresponding cds type.
340
- * @returns the corresponding cds type.
419
+ * @param {string | undefined} type - AsyncAPI primitive type
420
+ * @param {string | null} format - AsyncAPI format string
341
421
  */
342
422
  getDataType(type, format = null) {
423
+ if (!type) throw new Error(messages.UNRESOLVED_TYPE + `'${type}'`);
424
+ /** @type {Record<string, string>} */
343
425
  const asyncapiToCdsMapping = {
344
426
  'number': 'cds.Double',
345
427
  'boolean': 'cds.Boolean',
@@ -3,6 +3,7 @@ const { join, resolve, basename } = require('node:path')
3
3
  const { execSync } = require('node:child_process')
4
4
  const { fs, exists, isfile, isdir, path } = cds.utils
5
5
  const { parseXml } = require('./xml')
6
+ const YAML = require('yaml')
6
7
  const { REGEX_JAVA_VERSION, JAVA_LTS_VERSIONS } = require('./constants')
7
8
  const TRACE = cds.debug('trace')
8
9
 
@@ -69,6 +70,7 @@ module.exports = new class ProjectReader {
69
70
  readProject() {
70
71
  if (!this.appName) this.#setPackageInfo()
71
72
  const env = this.env4('production')
73
+ if (this.isJava) env.build.target = '.'
72
74
  const { appVersion, appName, appId, appDescription, jdkVersion } = this
73
75
  TRACE?.({ env })
74
76
 
@@ -249,8 +251,8 @@ module.exports._readSpringBootConfig = async function (roots = [cds.env.folders.
249
251
  for (let r of rootDirs) {
250
252
  const file = isfile(join(r, './src/main/resources/application.yaml'))
251
253
  if (file) {
252
- const yaml = cds.load.yaml(file)
253
- for (let yamlDoc of Array.isArray(yaml) ? yaml : [yaml]) {
254
+ const docs = YAML.parseAllDocuments(fs.readFileSync(file, 'utf-8'))
255
+ for (let yamlDoc of docs.map(d => d.toJS())) {
254
256
  let cds = yamlDoc?.cds;
255
257
  if (!cds) continue
256
258
  cds = _normalizeSpringBootCfg(cds)
@@ -4,7 +4,7 @@
4
4
  "@sap/ams": "^3"
5
5
  },
6
6
  "devDependencies": {
7
- "@sap/ams-dev": "^2"
7
+ "@sap/ams-dev": "^3"
8
8
  },
9
9
  "engines": {
10
10
  "node": "^24"
@@ -1,5 +1,5 @@
1
1
  const cds = require('../../../cds')
2
- const { exists, mkdirp, path } = cds.utils
2
+ const { fs: { promises: { cp } }, exists, mkdirp, path } = cds.utils
3
3
  const { OPTIONS: { NODEJS, JAVA } } = require('../../constants')
4
4
  const term = require('../../../util/term')
5
5
  const { merge } = require('../../merge')
@@ -24,10 +24,12 @@ module.exports = class NodejsTemplate extends require('../../plugin') {
24
24
 
25
25
  async run() {
26
26
  const { db, srv, app } = cds.env.folders
27
- if (cds.cli.command === 'init' && cds.cli.options.force) {
27
+ if (cds.cli.command === 'init' || cds.cli.options.force) {
28
28
  await renderAndCopy(path.join(__dirname, 'files'), cds.root, { projectName: path.basename(cds.root) })
29
29
  } else {
30
30
  await merge(__dirname, 'files/package.json.hbs').into('package.json', { with: { projectName: path.basename(cds.root) }})
31
+ await cp(path.join(__dirname, 'files/eslint.config.mjs'), 'eslint.config.mjs', { force: false })
32
+ await cp(path.join(__dirname, 'files/.vscode'), '.vscode', { recursive: true, force: false })
31
33
  }
32
34
  await Promise.all([db, srv, app].map(p => mkdirp(p)))
33
35
  }
@@ -19,7 +19,7 @@ describe('{{serviceName}} OData APIs', () => {
19
19
  it('serves <%name%>', async () => {
20
20
  const { data } = await GET `<%&urlPath%> ${{ params: { $select: '<%&selectString%>' } }}`
21
21
  expect(data.value).to.containSubset([
22
- <%&elementsString%>,
22
+ // <%&elementsString%>,
23
23
  ])
24
24
  })
25
25
  <%={{ }}=%>{{/entities}}
@@ -27,7 +27,8 @@ describe('{{serviceName}} OData APIs', () => {
27
27
  {{#actions}}{{=<% %>=}}
28
28
  it('executes <%name%>', async () => {
29
29
  const { data } = await POST `<%&urlPath%> ${
30
- <%&paramsString%>
30
+ {}
31
+ // <%&paramsString%>
31
32
  }`
32
33
  // TODO finish this test
33
34
  // expect(data.value).to...
@@ -86,13 +86,17 @@ module.exports = class XsuaaTemplate extends require('../../plugin') {
86
86
  if (addKyma || addMultitenancy && hasKyma) {
87
87
  const overwrites = addMultitenancy ? [{ item: 'xsuaa.parameters.tenant-mode', withValue: 'shared' }] : []
88
88
  await merge(__dirname, 'files/values.yaml.hbs').into('chart/values.yaml', { with: project, overwrites })
89
- await merge({
90
- xsuaa: {
91
- parameters: {
92
- xsappname: `${project.appName}-{{ .Release.Namespace }}`
93
- }
94
- }
95
- }).into('chart/values.yaml')
89
+ const parameters = {
90
+ xsappname: `${project.appName}-{{ .Release.Namespace }}`
91
+ }
92
+ if (!project.hasCustomRoleCollections && xsSecurity['role-templates'].length) {
93
+ parameters['role-collections'] = xsSecurity['role-templates'].map(role => ({
94
+ name: `${role.name} (${project.appName} {{ .Release.Namespace }})`,
95
+ description: 'generated',
96
+ 'role-template-references': [`$XSAPPNAME.${role.name}`],
97
+ }))
98
+ }
99
+ await merge({ xsuaa: { parameters } }).into('chart/values.yaml')
96
100
  await KymaTemplate.mergeDependency('service-instance', 'xsuaa')
97
101
 
98
102
  }
@@ -14,7 +14,7 @@ const CDS_TERMS = Object.freeze({
14
14
  Core: ['AcceptableMediaTypes', 'Computed', 'ComputedDefaultValue', 'DefaultNamespace', 'Description', 'Example', 'Immutable', 'LongDescription',
15
15
  'MediaType', 'OptionalParameter', 'Permissions', 'SchemaVersion'],
16
16
  JSON: ['Schema'],
17
- Validation: ['AllowedValues', 'Exclusive', 'Maximum', 'Minimum', 'Pattern']
17
+ Validation: ['AllowedValues', 'Exclusive', 'Maximum', 'MaxItems', 'Minimum', 'MinItems', 'Pattern']
18
18
  })
19
19
 
20
20
  /**
@@ -168,7 +168,7 @@ class CSDLMeta {
168
168
 
169
169
  /**
170
170
  * Construct map of qualified term names
171
- * @param {object} alias Map of namespace or alias to alias
171
+ * @param {*} alias Map of namespace or alias to alias
172
172
  */
173
173
  #getVocabularies(alias) {
174
174
  Object.keys(CDS_TERMS).forEach(vocab => {
@@ -187,7 +187,7 @@ class CSDLMeta {
187
187
  /**
188
188
  * Find model element by qualified name
189
189
  * @param {string} qname Qualified name of model element
190
- * @return {object} Model element
190
+ * @return {*} Model element
191
191
  */
192
192
  modelElement(qname) {
193
193
  const q = nameParts(qname);