@sap/cds-dk 8.2.3 → 8.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +66 -11
  2. package/bin/build.js +1 -1
  3. package/bin/cds.js +17 -4
  4. package/bin/compile/index.js +12 -3
  5. package/bin/deploy/index.js +22 -3
  6. package/bin/env.js +3 -2
  7. package/bin/import.js +24 -14
  8. package/bin/init.js +8 -4
  9. package/bin/repl.js +21 -8
  10. package/bin/util/fuzzySearch.js +8 -5
  11. package/lib/build/plugins/helm/files/subcharts/content-deployment/Chart.yaml +1 -1
  12. package/lib/build/plugins/helm/files/subcharts/content-deployment/values.schema.json +1 -1
  13. package/lib/build/plugins/helm/files/subcharts/content-deployment/values.yaml +1 -1
  14. package/lib/build/plugins/helm/files/subcharts/web-application/Chart.yaml +1 -1
  15. package/lib/build/plugins/helm/files/subcharts/web-application/templates/virtual-service.yaml +23 -12
  16. package/lib/build/plugins/helm/files/subcharts/web-application/values.schema.json +1 -1
  17. package/lib/build/plugins/helm/files/subcharts/web-application/values.yaml +2 -2
  18. package/lib/build/util.js +32 -20
  19. package/lib/compile/to-mermaid.js +87 -0
  20. package/lib/deploy/to-hana/hana.js +2 -21
  21. package/lib/env/schemas/cds-rc.js +174 -2
  22. package/lib/import/asyncapi/parserContext.js +4 -30
  23. package/lib/import/common.js +2 -2
  24. package/lib/import/importapi.js +3 -3
  25. package/lib/import/odata/v2parser.js +3 -2
  26. package/lib/import/openapi/importOpenAPI.js +622 -537
  27. package/lib/import/openapi/utilities.js +7 -2
  28. package/lib/init/constants.js +2 -1
  29. package/lib/init/index.js +4 -0
  30. package/lib/init/merge.js +2 -2
  31. package/lib/init/mvn.js +2 -2
  32. package/lib/init/projectReader.js +2 -1
  33. package/lib/init/registries/mta.js +8 -1
  34. package/lib/init/registries/xs-app.js +10 -0
  35. package/lib/init/template/approuter/files/package.json +1 -1
  36. package/lib/init/template/cf-manifest/files/manifest.yml.hbs +3 -1
  37. package/lib/init/template/cf-manifest/index.js +2 -0
  38. package/lib/init/template/cloud-logging/files/mta.yaml.hbs +13 -2
  39. package/lib/init/template/cloud-logging/files/{package.json → package.json.hbs} +5 -1
  40. package/lib/init/template/cloud-logging/index.js +4 -3
  41. package/lib/init/template/data/as-csv.js +6 -0
  42. package/lib/init/template/data/as-json.js +8 -1
  43. package/lib/init/template/data/index.js +1 -1
  44. package/lib/init/template/destination/files/mta.yaml.hbs +5 -5
  45. package/lib/init/template/dynatrace/files/mta.yaml.hbs +22 -6
  46. package/lib/init/template/dynatrace/files/package.json.hbs +5 -2
  47. package/lib/init/template/dynatrace/files/values.yaml +15 -0
  48. package/lib/init/template/dynatrace/index.js +10 -5
  49. package/lib/init/template/enterprise-messaging-shared/index.js +1 -1
  50. package/lib/init/template/file-based-messaging/index.js +1 -1
  51. package/lib/init/template/hana/files/package.json.hbs +2 -0
  52. package/lib/init/template/hana/index.js +1 -1
  53. package/lib/init/template/handler/index.js +16 -1
  54. package/lib/init/template/helm/files/values.yaml.hbs +2 -2
  55. package/lib/init/template/helm/index.js +6 -1
  56. package/lib/init/template/html5-repo/files/app-package.json.hbs +3 -1
  57. package/lib/init/template/html5-repo/files/ui5-deploy.yaml.hbs +7 -0
  58. package/lib/init/template/html5-repo/index.js +3 -1
  59. package/lib/init/template/local-messaging/index.js +1 -1
  60. package/lib/init/template/mta/files/mta.yaml.hbs +1 -1
  61. package/lib/init/template/mta/index.js +2 -0
  62. package/lib/init/template/nodejs/files/.vscode/extensions.json +0 -5
  63. package/lib/init/template/nodejs/files/.vscode/settings.json +1 -1
  64. package/lib/init/template/nodejs/files/.vscode/tasks.json +0 -2
  65. package/lib/init/template/nodejs/files/package.json.hbs +1 -1
  66. package/lib/init/template/postgres/index.js +1 -1
  67. package/lib/init/template/redis-messaging/index.js +1 -1
  68. package/lib/init/template/side-by-side-extensibility/files/mta.yaml.hbs +40 -0
  69. package/lib/init/template/side-by-side-extensibility/files/package.json.hbs +17 -0
  70. package/lib/init/template/side-by-side-extensibility/index.js +34 -0
  71. package/lib/init/template/typer/index.js +2 -1
  72. package/lib/init/template/typescript/files/package.json +2 -2
  73. package/lib/init/template/workzone-standard/files/manifest.json.hbs +15 -1
  74. package/lib/init/template/workzone-standard/files/mta.yaml.hbs +11 -2
  75. package/lib/init/template/workzone-standard/files/xs-app.json.hbs +18 -0
  76. package/lib/init/template/workzone-standard/index.js +25 -7
  77. package/lib/init/template/xsuaa/index.js +1 -1
  78. package/lib/mtx/auth_manager.js +7 -5
  79. package/lib/mtx/util/errors.js +4 -4
  80. package/lib/util/fs.js +1 -1
  81. package/lib/util/jsonc.js +1 -1
  82. package/lib/util/jsonc.pegjs +6 -2
  83. package/npm-shrinkwrap.json +78 -149
  84. package/package.json +1 -1
package/lib/build/util.js CHANGED
@@ -6,6 +6,7 @@ const execAsync = require('util').promisify(cp.exec)
6
6
  const IS_WIN = require('os').platform() === 'win32'
7
7
  const { SEVERITY_ERROR, FILE_EXT_CDS } = require('./constants');
8
8
  const DEBUG = cds.debug('cli|build')
9
+ const SECRETS = /(passw)|(cert)|(ca)|(secret)|(key)/i
9
10
 
10
11
  function getProperty(src, segments) {
11
12
  segments = Array.isArray(segments) ? segments : segments.split('.')
@@ -68,31 +69,42 @@ function relativePaths(root, qualifiedPaths) {
68
69
  return qualifiedPaths
69
70
  }
70
71
 
71
- function redactCredentials(config, o) {
72
- return JSON.stringify(config, (k, v) => {
73
- if (!v) {
74
- return v
75
- }
76
- if (k === 'credentials') {
77
- try {
78
- return _redacted(v)
79
- } catch (e) {/* ignored */ }
72
+ function redactCredentials(config) {
73
+ if (typeof config !== 'object' || config === null) {
74
+ return config;
75
+ }
76
+
77
+ if (Array.isArray(config)) {
78
+ return config.map(item => redactCredentials(item));
79
+ } else {
80
+ const newConfig = Object.assign({}, config);
81
+ for (let key in newConfig) {
82
+ if (key === 'credentials') {
83
+ newConfig[key] = _redacted(newConfig[key]);
84
+ } else {
85
+ newConfig[key] = redactCredentials(newConfig[key]);
86
+ }
80
87
  }
81
- return v
82
- }, o && o.indents || 2)
88
+ return newConfig;
89
+ }
83
90
  }
84
91
 
85
- /** redacts password-like strings, also reducing clutter in output */
92
+ /**
93
+ * Masks password-like strings, also reducing clutter in output
94
+ * @param {any} cred - object or array with credentials
95
+ * @returns {any}
96
+ */
86
97
  function _redacted(cred) {
87
- const secrets = /(password)|(certificate)|(ca)/i // 'certificate' and 'ca' on HANA
88
- const newCred = Object.assign({}, cred)
89
- Object.keys(newCred)
90
- .filter(k => typeof newCred[k] === 'string' && secrets.test(k))
91
- .forEach(k => newCred[k] = '...')
92
- return newCred
98
+ if (!cred) return cred
99
+ if (Array.isArray(cred)) return cred.map(c => typeof c === 'string' ? '...' : _redacted(c))
100
+ if (typeof cred === 'object') {
101
+ const newCred = Object.assign({}, cred)
102
+ Object.keys(newCred).forEach(k => (typeof newCred[k] === 'string' && SECRETS.test(k)) ? (newCred[k] = '...') : (newCred[k] = _redacted(newCred[k])))
103
+ return newCred
104
+ }
105
+ return cred
93
106
  }
94
-
95
-
107
+
96
108
  /**
97
109
  * Returns a list of fully qualified model names belonging to the '@sap' namespace that cannot be resolved.
98
110
  * E.g. the module might NOT have been installed.
@@ -1,6 +1,8 @@
1
1
  const cds = require('../cds')
2
2
 
3
3
  module.exports = function to_mermaid (model, options={}) {
4
+ if (typeof model === 'string' && /mta.*\.ya?ml/.test(model)) return to_mta_mermaid(model)
5
+
4
6
  options = Object.assign({
5
7
  assocnames: false, // include association names
6
8
  elements : false, // include all elements { falsy | 'all'|true | 'keys' }
@@ -119,3 +121,88 @@ function resolveTargets (query) {
119
121
 
120
122
  return targets
121
123
  }
124
+
125
+ function to_mta_mermaid (file) {
126
+ const content = cds.utils.fs.readFileSync(file, 'utf8')
127
+ const mta = cds.utils.yaml.parse(content)
128
+ const { modules = [], resources = [] } = mta
129
+
130
+ const all = new Set([...modules.map(m => m.name), ...resources.map(r => r.name)])
131
+ const provided = new Set()
132
+ const required = new Set()
133
+
134
+ let diag = 'graph LR\n'
135
+
136
+ const appModules = modules.filter(module => module.provides && module.provides.length > 0)
137
+ const utilityModules = modules.filter(module => !module.provides || module.provides.length === 0)
138
+
139
+ diag += ' subgraph Modules\n'
140
+ appModules.forEach(module => {
141
+ diag += ` subgraph ${module.name}\n`
142
+ module.provides.forEach(provide => {
143
+ provided.add(provide.name)
144
+ diag += ` ${provide.name}["${provide.name}"]\n`
145
+ })
146
+ diag += ` end\n`
147
+ })
148
+ utilityModules.forEach(module => {
149
+ diag += ` ${module.name}["${module.name}"]:::module\n`
150
+ })
151
+ diag += ' end\n'
152
+
153
+ diag += ' subgraph Resources\n'
154
+ resources.forEach(resource => {
155
+ diag += ` ${resource.name}["${resource.name}"]:::resource\n`
156
+ })
157
+ diag += ' end\n'
158
+
159
+ modules.forEach(module => {
160
+ if (module.requires) {
161
+ module.requires.forEach(require => {
162
+ required.add(require.name)
163
+ if (!all.has(require.name) && !provided.has(require.name)) {
164
+ diag += ` ${module.name} -->|requires| ${require.name}:::missing\n`
165
+ } else {
166
+ diag += ` ${module.name} -->|requires| ${require.name}\n`
167
+ }
168
+ })
169
+ }
170
+ })
171
+ resources.forEach(resource => {
172
+ if (resource.requires) {
173
+ resource.requires.forEach(require => {
174
+ required.add(require.name)
175
+ if (!all.has(require.name) && !provided.has(require.name)) {
176
+ diag += ` ${resource.name} -->|requires| ${require.name}:::missing\n`
177
+ } else {
178
+ diag += ` ${resource.name} -->|requires| ${require.name}\n`
179
+ }
180
+ })
181
+ }
182
+ })
183
+
184
+ provided.forEach(api => {
185
+ if (!required.has(api)) {
186
+ diag += ` class ${api} unused\n`
187
+ } else {
188
+ diag += ` class ${api} api\n`
189
+ }
190
+ })
191
+ modules.forEach(module => {
192
+ diag += ` class ${module.name} module\n`
193
+ })
194
+ resources.forEach(resource => {
195
+ diag += ` class ${resource.name} resource\n`
196
+ })
197
+
198
+ diag += `
199
+ classDef module fill:#36aeff,stroke:#333,stroke-width:2px;
200
+ classDef resource fill:#2bab43,stroke:#333,stroke-width:2px;
201
+ classDef api fill:#e03da4,stroke:#333,stroke-width:2px;
202
+ classDef missing fill:#ffcccc,stroke:#ff0000,stroke-width:2px,color:#ff0000;
203
+ classDef unused fill:#ffcccc,stroke:#ff0000,stroke-width:2px,color:#ff0000;
204
+ `
205
+
206
+ return diag
207
+ }
208
+
@@ -38,7 +38,7 @@ module.exports = new class HanaDeployer {
38
38
  async deploy(options = {}) {
39
39
  let { model, serviceName: pServiceName, tunnelAddress, vcapFile, undeployWhitelist, hdiOptions = {}, dry, storeCredentials } = options;
40
40
 
41
- options.for = options.for || 'hybrid';
41
+ options.for ??= 'hybrid';
42
42
 
43
43
  if (model) {
44
44
  throw `Specifying a CDS model for HANA deployment is not supported.
@@ -168,7 +168,7 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
168
168
  console.log(`Using container ${bold(cfServiceInstanceName)}`);
169
169
 
170
170
  const cfConfig = cfServiceInstanceMta?.parameters?.config;
171
- const serviceInstance = await this.createHanaService(cfServiceInstanceName, cfConfig);
171
+ const serviceInstance = await cfUtil.getOrCreateService('hana', 'hdi-shared', cfServiceInstanceName, cfConfig);
172
172
 
173
173
  const cfServiceInstanceKeyName = serviceKeyName ?? `${cfServiceInstanceName}-key`;
174
174
  let credentials = await cfUtil.getOrCreateServiceKey(serviceInstance, cfServiceInstanceKeyName, { permissions: 'development' });
@@ -182,25 +182,6 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
182
182
  return { cfServiceInstanceName, cfServiceInstanceKeyName, serviceKey: credentials, serviceInstance }
183
183
  }
184
184
 
185
-
186
- async createHanaService(instanceName, cfConfig) {
187
- // hana or hanatrial, error if neither found
188
- try {
189
- return await cfUtil.getOrCreateService('hana', 'hdi-shared', instanceName, cfConfig);
190
- } catch (error) {
191
- if (error.command && /offering .* not found/i.test(error.command.stderr)) {
192
- console.log(`Falling back to 'hanatrial'`);
193
- return await cfUtil.getOrCreateService('hanatrial', 'hdi-shared', instanceName, cfConfig);
194
- }
195
- else if (error.command && /no database/i.test(error.command.stderr)) {
196
- console.log(`No database connected to 'hana' service. Falling back to 'hanatrial'`);
197
- return await cfUtil.getOrCreateService('hanatrial', 'hdi-shared', instanceName, cfConfig);
198
- }
199
- throw error;
200
- }
201
- }
202
-
203
-
204
185
  _validateCredentials(credentials, cfServiceInstanceKey) {
205
186
  if (!credentials) {
206
187
  throw `Could not create service key ${bold(cfServiceInstanceKey)}.`;
@@ -131,6 +131,141 @@ module.exports = {
131
131
  }
132
132
  },
133
133
 
134
+ import: {
135
+ type: "object",
136
+ description: "Set CDS import configurations.",
137
+ additionalProperties: false,
138
+ properties: {
139
+ as: {
140
+ type: "string",
141
+ description: "Converts the input file into one of these supported formats: cds, csn or json.",
142
+ $ref: "#/$defs/import-options/as"
143
+ },
144
+ from: {
145
+ type: "string",
146
+ description: "Imports the model from the one of the given options: edmx, openapi or asyncapi.",
147
+ $ref: "#/$defs/import-options/from"
148
+ },
149
+ force: {
150
+ type: "boolean",
151
+ description: "Forcefully overwrites the contents in the CDS file and updates the checksum",
152
+ },
153
+ no_save: {
154
+ type: "boolean",
155
+ description: "Skips updating ./package.json.",
156
+ },
157
+ out: {
158
+ type: "string",
159
+ description: "Output file written to the specified location."
160
+ },
161
+ dry: {
162
+ type: "boolean",
163
+ description: "Writes the converted csn to stdout only.",
164
+ },
165
+ keepNamespace: {
166
+ type: "boolean",
167
+ description: "Keeps the original namespace from the edmx content instead of using the filename by default.",
168
+ default: false
169
+ },
170
+ includeNamespaces: {
171
+ type: "string",
172
+ description: "Imports only entities matching to the given list of namespaces."
173
+ }
174
+ }
175
+ },
176
+
177
+ export: {
178
+ type: "object",
179
+ description: "Customize cds compile settings",
180
+ additionalProperties: false,
181
+ properties: {
182
+ asyncapi: {
183
+ type: "object",
184
+ description: "AsyncAPI settings for cds compile",
185
+ additionalProperties: false,
186
+ properties: {
187
+ application_namespace: {
188
+ type: "string",
189
+ description: "The registered namespace of the application"
190
+ },
191
+ merged: {
192
+ type: "object",
193
+ description: "properties that needs to be there when merged option is used",
194
+ additionalProperties: false,
195
+ properties: {
196
+ title: {
197
+ type: "string",
198
+ description: "Title to be used for merged doc"
199
+ },
200
+ version: {
201
+ type: "string",
202
+ description: "Version to be used for merged doc"
203
+ },
204
+ short_text: {
205
+ type: "string",
206
+ description: "Short decription of the AsyncAPIs"
207
+ },
208
+ description: {
209
+ type: "string",
210
+ description: "Description to be used for the merged doc"
211
+ }
212
+ }
213
+ },
214
+ event_spec_version: {
215
+ type: "string",
216
+ description: "Mentions the version of AsyncAPI spec"
217
+ },
218
+ event_source: {
219
+ type: "string",
220
+ description: "Event source information",
221
+ default: "{region}/{applicationNamespace}/{instanceId}",
222
+ },
223
+ event_source_params: {
224
+ type: "object",
225
+ description: "Parameters related to the source of event",
226
+ default: {
227
+ region: {
228
+ "description": "The regional context of the application.",
229
+ "schema": {
230
+ "type": "string"
231
+ }
232
+ },
233
+ applicationNamespace: {
234
+ "description": "The registered namespace of the application.",
235
+ "schema": {
236
+ "type": "string"
237
+ }
238
+ },
239
+ instanceId: {
240
+ "description": "The instance id (tenant, installation, ...) of the application.",
241
+ "schema": {
242
+ "type": "string"
243
+ }
244
+ }
245
+ },
246
+ additionalProperties: false,
247
+ region: {
248
+ type: "string",
249
+ description: "The regional context of the application"
250
+ },
251
+ applicationNamespace: {
252
+ type: "string",
253
+ description: "The registered namespace of the application"
254
+ },
255
+ instanceId: {
256
+ type: "string",
257
+ description: "The instance id (tenant, installation, ...) of the application."
258
+ }
259
+ },
260
+ event_characteristics: {
261
+ type: "object",
262
+ description: "The characteristics defined for events",
263
+ additionalProperties: true
264
+ }
265
+ }
266
+ }
267
+ }
268
+ },
134
269
  // deploy specific schema
135
270
  deploy: {
136
271
  type: "object",
@@ -164,7 +299,8 @@ module.exports = {
164
299
  "mtx-extension",
165
300
  "mtx",
166
301
  "node-cf",
167
- "java-cf"
302
+ "java-cf",
303
+ "helm"
168
304
  ],
169
305
  enumDescriptions: [
170
306
  "Node.js build plugin",
@@ -174,7 +310,8 @@ module.exports = {
174
310
  "MTX extension build plugin",
175
311
  "MTX build plugin",
176
312
  "Node.js Cloud Foundry build plugin",
177
- "Java Cloud Foundry build plugin"
313
+ "Java Cloud Foundry build plugin",
314
+ "Helm chart build plugin"
178
315
  ]
179
316
  },
180
317
 
@@ -193,6 +330,41 @@ module.exports = {
193
330
  }
194
331
  }
195
332
  ]
333
+ },
334
+
335
+ import_options: {
336
+ from: {
337
+ oneOf: [
338
+ {
339
+ const: "asyncapi",
340
+ description: "AsyncAPI specification"
341
+ },
342
+ {
343
+ const: "edmx",
344
+ description: "OData specification"
345
+ },
346
+ {
347
+ const: "openapi",
348
+ description: "OpenAPI specification"
349
+ }
350
+ ]
351
+ },
352
+ as: {
353
+ oneOf: [
354
+ {
355
+ const: "cds",
356
+ description: "Converts the input file into cds format"
357
+ },
358
+ {
359
+ const: "csn",
360
+ description: "Converts the input file into csn format"
361
+ },
362
+ {
363
+ const: "json",
364
+ description: "Converts the input file into json format"
365
+ }
366
+ ]
367
+ }
196
368
  }
197
369
  }
198
370
  }
@@ -6,12 +6,11 @@ module.exports = class ParserContext {
6
6
  * @constructor
7
7
  */
8
8
  constructor(content) {
9
- this.topics = this.getTopics(content);
10
9
  this.services = this.getServicesFromContent(content);
11
10
  }
12
11
 
13
- getTopicName(channelName) {
14
- return channelName.startsWith('ce/') ? channelName.substring(3) : channelName;
12
+ getTopicName(eventName) {
13
+ return eventName.startsWith('ce/') ? eventName.substring(3) : eventName;
15
14
  }
16
15
 
17
16
  getEventName(messageRefString) {
@@ -19,28 +18,6 @@ module.exports = class ParserContext {
19
18
  return lastSlashIndex == -1 ? '' : messageRefString.substring(lastSlashIndex + 1);
20
19
  }
21
20
 
22
- /**
23
- * Return an obect with topic annotations for messages
24
- */
25
- getTopics(content) {
26
- const topics = {};
27
-
28
- Object.entries(content.channels).forEach(([key, value]) => {
29
- const topic = this.getTopicName(key);
30
- if (value.subscribe && value.subscribe.message && value.subscribe.message.$ref) {
31
- const event = this.getEventName(value.subscribe.message.$ref);
32
- topics[event] = topic;
33
- }
34
-
35
- if (value.publish && value.publish.message && value.publish.message.$ref) {
36
- const event = this.getEventName(value.publish.message.$ref);
37
- topics[event] = topic;
38
- }
39
- });
40
-
41
- return topics;
42
- }
43
-
44
21
  /**
45
22
  * Returns services from the asyncapi json object along with the events and annotations.
46
23
  * @returns an object with all the services.
@@ -49,7 +26,7 @@ module.exports = class ParserContext {
49
26
  const services = {};
50
27
  const serviceAnnotations = this.getServiceAnnotations(content);
51
28
 
52
- Object.entries(content.components.messages).forEach(([key, value]) => {
29
+ Object.entries(content.components.messages).forEach(([, value]) => {
53
30
  let eventName;
54
31
  // Message name should be used to capture the event name
55
32
  if (value.name) {
@@ -71,10 +48,7 @@ module.exports = class ParserContext {
71
48
  const eventAnnotations = this.getEventAnnotations(value);
72
49
 
73
50
  // add topic annotation
74
- if (this.topics[key]) {
75
- eventAnnotations['@topic'] = this.topics[key];
76
- }
77
-
51
+ eventAnnotations['@topic'] = this.getTopicName(eventName);
78
52
  let properties = {};
79
53
  let types = {};
80
54
 
@@ -17,7 +17,7 @@ let optionsMapping = {
17
17
  };
18
18
 
19
19
  let apiOptions = ['includeNamespaces', 'keepNamespace', 'beta'];
20
- let commonOptions = ['no_copy', 'no_save', 'out', 'dry', 'as', 'from', 'force', 'config', 'destination'];
20
+ let commonOptions = ['no_copy', 'no_save', 'out', 'dry', 'as', 'from', 'force', 'config', 'destination', 'name'];
21
21
 
22
22
  async function generateEDMX2JSON(edmx) {
23
23
  let cjson = convert.xml2json(edmx, {
@@ -344,7 +344,7 @@ async function _updatePackageJson(dest, services, kind, cwd, config, destination
344
344
  if (!config && destination) {
345
345
  config = { "[production]": { credentials: { destination } } };
346
346
  }
347
-
347
+
348
348
  let package_json_updated = false;
349
349
  for (let service of services) {
350
350
  // existing service configuration data can be updated, e.g. in case of delta imports
@@ -7,11 +7,11 @@ let asyncapi = require('./asyncapi');
7
7
  let messages = require('./message').getMessages();
8
8
 
9
9
  module.exports = Object.assign(cds_import, {
10
- from: cds.lazified({
10
+ from: {
11
11
  edmx: cds_import_from_edmx,
12
12
  openapi: cds_import_from_openapi,
13
13
  asyncapi: cds_import_from_asyncapi
14
- })
14
+ }
15
15
  });
16
16
 
17
17
  /**
@@ -95,7 +95,7 @@ async function cds_import_from_openapi(filepath, options = {}) {
95
95
  return csn;
96
96
  }
97
97
 
98
- /**
98
+ /**
99
99
  * This is the import API function to generate CSN from asyncapi files.
100
100
  * @param {string} filepath
101
101
  * - a single valid filepath
@@ -1650,8 +1650,9 @@ function _generateEDMXJSON2CSN(jsonObj, ignorePersistenceSkip, parserContext) {
1650
1650
  }
1651
1651
 
1652
1652
  // Construct CSN entity based on EDMX entity
1653
- let entityJson = jsonObj["edmx:Edmx"]["edmx:DataServices"].Schema.EntityType;
1654
- if (!Array.isArray(entityJson)) entityJson = [entityJson];
1653
+ let entityJson = jsonObj["edmx:Edmx"]["edmx:DataServices"].Schema.EntityType;
1654
+ // if entityJson exists(ignoring the case of no entity) and is not an array, convert it to an array to handle the case of single entity
1655
+ if (entityJson && !Array.isArray(entityJson)) entityJson = [entityJson];
1655
1656
  const definitions = [];
1656
1657
 
1657
1658
  if (parserContext.mockServerUc) {