@sap/cds-dk 6.3.2 → 6.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 (93) hide show
  1. package/CHANGELOG.md +38 -7
  2. package/bin/cds.js +2 -0
  3. package/bin/compile/index.js +2 -1
  4. package/bin/deploy/index.js +5 -3
  5. package/bin/deploy/to-sqlite.js +17 -15
  6. package/bin/help.js +7 -4
  7. package/bin/init.js +1 -1
  8. package/bin/login.js +3 -4
  9. package/bin/migrate.js +33 -0
  10. package/bin/pull.js +4 -4
  11. package/bin/push.js +1 -1
  12. package/bin/subscribe.js +58 -66
  13. package/bin/unsubscribe.js +67 -0
  14. package/bin/watch.js +4 -3
  15. package/lib/bind/bindingUtil.js +2 -0
  16. package/lib/bind/cfKeyProvider.js +5 -1
  17. package/lib/client/activate.js +3 -3
  18. package/lib/client/auth_manager.js +2 -2
  19. package/lib/client/base_command.js +7 -6
  20. package/lib/client/extend.js +2 -2
  21. package/lib/client/helper/errors.js +8 -2
  22. package/lib/client/helper/logging.js +13 -11
  23. package/lib/client/params.js +3 -0
  24. package/lib/client/pull.js +1 -1
  25. package/lib/client/push.js +4 -4
  26. package/lib/client/settings_manager.js +13 -6
  27. package/lib/client/subscription.js +81 -0
  28. package/lib/compile/to-xsuaa.js +25 -19
  29. package/lib/import/odata/metadataconverter/MetadataConverter.js +3 -3
  30. package/lib/import/odata/v2parser.js +37 -24
  31. package/lib/import/odata/v4parser.js +111 -37
  32. package/lib/init/constants.js +1 -1
  33. package/lib/init/index.js +2 -2
  34. package/lib/init/template/_merging/registry-mta.js +0 -3
  35. package/lib/init/template/approuter/files/approuter-configmap.yaml +10 -0
  36. package/lib/init/template/approuter/files/approuter-mtxs-configmap.yaml +15 -0
  37. package/lib/init/template/approuter/files/chart.yaml +4 -0
  38. package/lib/init/template/approuter/files/package.json +1 -1
  39. package/lib/init/template/approuter/files/values.yaml.hbs +31 -0
  40. package/lib/init/template/approuter/index.js +60 -12
  41. package/lib/init/template/auditlog/index.js +16 -8
  42. package/lib/init/template/cf-manifest/index.js +3 -3
  43. package/lib/init/template/connectivity/index.js +19 -10
  44. package/lib/init/template/destinations/index.js +23 -7
  45. package/lib/init/template/enterprise-messaging/files/cds.package.json +1 -2
  46. package/lib/init/template/enterprise-messaging/files/cds.profile.package.json.hbs +1 -2
  47. package/lib/init/template/enterprise-messaging/files/cdsrc.json +1 -2
  48. package/lib/init/template/enterprise-messaging/files/cdsrc.json.hbs +1 -2
  49. package/lib/init/template/enterprise-messaging/files/event-mesh.json.hbs +31 -0
  50. package/lib/init/template/enterprise-messaging/files/mta.yaml.hbs +1 -0
  51. package/lib/init/template/enterprise-messaging/index.js +26 -13
  52. package/lib/init/template/extensibility/index.js +19 -10
  53. package/lib/init/template/hana/files/mta.yaml.hbs +0 -4
  54. package/lib/init/template/hana/index.js +58 -22
  55. package/lib/init/template/helm/chart/templates/NOTES.txt +4 -3
  56. package/lib/init/template/helm/chart/templates/_deployment_helpers.tpl +25 -2
  57. package/lib/init/template/helm/chart/values.root.schema.json +5 -0
  58. package/lib/init/template/helm/chart/values.schema.json +27 -0
  59. package/lib/init/template/helm/files/values.yaml.hbs +1 -1
  60. package/lib/init/template/helm/index.js +25 -23
  61. package/lib/init/template/helm/subchart/templates/_helpers.tpl +29 -0
  62. package/lib/init/template/helm/subchart/templates/api-rule.yaml +5 -5
  63. package/lib/init/template/helm/subchart/templates/deployment.yaml +12 -9
  64. package/lib/init/template/helm/subchart/values.md +258 -88
  65. package/lib/init/template/helm/subchart/values.schema.json +96 -13
  66. package/lib/init/template/helm/subchart/values.yaml +6 -2
  67. package/lib/init/template/html5-repo/index.js +28 -11
  68. package/lib/init/template/java/index.js +5 -5
  69. package/lib/init/template/kibana-logging/index.js +5 -4
  70. package/lib/init/template/mta/index.js +2 -2
  71. package/lib/init/template/mtx/index.js +1 -1
  72. package/lib/init/template/multitenancy/files/chart.yaml +4 -0
  73. package/lib/init/template/multitenancy/files/java/mtxs-configmap.yaml +20 -0
  74. package/lib/init/template/multitenancy/files/java/package.json +6 -9
  75. package/lib/init/template/multitenancy/files/mtxs-configmap.yaml +15 -0
  76. package/lib/init/template/multitenancy/files/saas-registry.yaml +17 -19
  77. package/lib/init/template/multitenancy/files/values.yaml.hbs +47 -0
  78. package/lib/init/template/multitenancy/files/xs-security.json.hbs +0 -7
  79. package/lib/init/template/multitenancy/index.js +83 -37
  80. package/lib/init/template/nodejs/files/.eslintrc +26 -0
  81. package/lib/init/template/nodejs/files/package.json.hbs +0 -28
  82. package/lib/init/template/nodejs/index.js +7 -10
  83. package/lib/init/template/pipeline/index.js +4 -4
  84. package/lib/init/template/samples/index.js +5 -5
  85. package/lib/init/template/templateBase.js +1 -4
  86. package/lib/init/template/toggles/index.js +5 -5
  87. package/lib/init/template/xsuaa/files/values.yaml.hbs +21 -0
  88. package/lib/init/template/xsuaa/index.js +34 -24
  89. package/lib/init/util/md5Tracking.js +113 -0
  90. package/lib/init/util/merge.js +19 -2
  91. package/lib/init/util/projectReader.js +15 -12
  92. package/npm-shrinkwrap.json +211 -686
  93. package/package.json +3 -3
@@ -2,11 +2,17 @@ const { getMessage } = require('./logging');
2
2
 
3
3
  class CliError extends Error {
4
4
  // noinspection JSAnnotator
5
- constructor(message, error) {
6
- throw getMessage(message, error);
5
+ constructor(message, { error, command } = {}) {
6
+ const help = command && getCommandHelp(command);
7
+ throw getMessage(message, { error, help });
7
8
  }
8
9
  }
9
10
 
11
+ function getCommandHelp(command) {
12
+ const cli = require('../../../bin/cds');
13
+ return cli?.load(command, () => ({ help: '' }))?.help ?? '';
14
+ }
15
+
10
16
  module.exports = {
11
17
  CliError
12
18
  };
@@ -2,22 +2,24 @@
2
2
 
3
3
  function debug(debugText, error) {
4
4
  if (process.env.DEBUG) {
5
- console.log(getMessage(debugText, error));
5
+ console.log(getMessage(debugText, { error }));
6
6
  }
7
7
  }
8
8
 
9
- function getMessage(errorText, error) {
9
+ function getMessage(errorText, { error, help } = {}) {
10
10
  let message = errorText ? errorText : '';
11
- if (!error) {
12
- return message;
11
+ if (error) {
12
+ message += '\n' + (
13
+ error.response?.data?.message // response body == error
14
+ || error.response?.data?.error?.message // response body == { error }
15
+ || error.message // no response attached
16
+ );
17
+ if (process.env.DEBUG && error.stack) {
18
+ message += '\n' + error.stack;
19
+ }
13
20
  }
14
- message += '\n' + (
15
- error.response?.data?.message // response body == error
16
- || error.response?.data?.error?.message // response body == { error }
17
- || error.message // no response attached
18
- );
19
- if (process.env.DEBUG && error.stack) {
20
- message += '\n' + error.stack;
21
+ if (typeof help === 'string') {
22
+ message += require('../../../bin/help').formatHelp(help);
21
23
  }
22
24
  return message;
23
25
  }
@@ -210,6 +210,9 @@ class AllParams extends BasicParams {
210
210
  this.wsdl = new Param('wsdl', false);
211
211
  this.sync = new Param('sync', false);
212
212
 
213
+ // `login` PARAMS
214
+ this.skipToken = new Param('skipToken', false);
215
+
213
216
  // `logout` PARAMS
214
217
  this.deleteSettings = new Param('deleteSettings', false);
215
218
  this["delete-settings"] = this.deleteSettings.newAlias('delete-settings');
@@ -41,6 +41,6 @@ module.exports = class Pull extends BaseCommand {
41
41
 
42
42
  return axios.post(pullUrl, {}, options)
43
43
  .then(response => Buffer.from(response.data))
44
- .catch(error => this.handleHttpError(error, params, pullUrl));
44
+ .catch(error => this.handleHttpError(error, params, { url: pullUrl }));
45
45
  }
46
46
  }
@@ -33,12 +33,12 @@ module.exports = class Push extends BaseCommand {
33
33
  src = local(src);
34
34
  } else {
35
35
  if (!schemaRegex.test(src)) {
36
- throw new CliError(`Nonexistent path: ${src}`);
36
+ throw new CliError(`Nonexistent path: ${src}`, { command: 'push' });
37
37
  }
38
38
  try {
39
39
  content = (await axios.get(src, { responseEncoding: 'base64' })).data;
40
40
  } catch (error) {
41
- this.handleHttpError(error, params, src);
41
+ this.handleHttpError(error, params, { url: src });
42
42
  }
43
43
  }
44
44
  return { src, content };
@@ -48,7 +48,7 @@ module.exports = class Push extends BaseCommand {
48
48
  try {
49
49
  return require(join(projectFolder, 'package.json')).name;
50
50
  } catch (error) {
51
- throw new CliError(`Extension project at ${projectFolder} is missing package.json file`, error);
51
+ throw new CliError(`Extension project at ${projectFolder} is missing package.json file`, { error, command: 'push' });
52
52
  }
53
53
  }
54
54
 
@@ -60,6 +60,6 @@ module.exports = class Push extends BaseCommand {
60
60
  extension: tgz,
61
61
  tag: extensionName
62
62
  }, options)
63
- .catch(error => this.handleHttpError(error, params, pushUrl));
63
+ .catch(error => this.handleHttpError(error, params, { url: pushUrl }));
64
64
  }
65
65
  }
@@ -171,7 +171,7 @@ class SettingsManager {
171
171
  }
172
172
  await this._saveToFile(params.get('projectFolder'), paramsMap);
173
173
 
174
- if (!params.has('token')) {
174
+ if (params.get('skipToken') || !params.has('token')) {
175
175
  return;
176
176
  }
177
177
 
@@ -206,6 +206,9 @@ class SettingsManager {
206
206
 
207
207
  params.mergeLower(loadedSettings);
208
208
 
209
+ if (params.has('username')) {
210
+ params.set('skipToken', true);
211
+ }
209
212
  await this.updateUrls(params, logout, loadedAppUrl);
210
213
  await this.checkKeytar(params, logout);
211
214
  await this.addAuth(params, logout);
@@ -216,7 +219,7 @@ class SettingsManager {
216
219
  async function addAppUrlAndSubdomain() {
217
220
  function needsSubdomain() {
218
221
  // Subdomain needed for token retrieval.
219
- return params.has('appUrl') && !localhostRegex.test(params.get('appUrl')) && !params.has('username');
222
+ return params.has('appUrl') && !localhostRegex.test(params.get('appUrl')) && !params.get('skipToken');
220
223
  }
221
224
 
222
225
  // Prefix URL schema
@@ -273,12 +276,15 @@ class SettingsManager {
273
276
 
274
277
  await addAppUrlAndSubdomain();
275
278
 
276
- if (!logout) {
279
+ if (!logout && !params.get('skipToken')) {
277
280
  await renewTokenUrl();
278
281
  }
279
282
  }
280
283
 
281
284
  static async checkKeytar(params, logout) {
285
+ if (params.get('skipToken')) {
286
+ return;
287
+ }
282
288
  if (isBas()) {
283
289
  if (params.get('tokenStorage') === 'keyring') {
284
290
  console.log('NOTE:', capitalize(TOKEN_STORAGE_DESC.keyring), 'not supported on SAP Business Application Studio. Switching to', TOKEN_STORAGE_DESC.plain, '.');
@@ -286,9 +292,6 @@ class SettingsManager {
286
292
  }
287
293
  return;
288
294
  }
289
- if (params.get('username')) {
290
- return;
291
- }
292
295
  keytar = requireGlobal('keytar');
293
296
  if (keytar) {
294
297
  return;
@@ -378,6 +381,10 @@ class SettingsManager {
378
381
  params.delete('isEmptyPassword');
379
382
  }
380
383
 
384
+ if (params.get('skipToken')) {
385
+ return;
386
+ }
387
+
381
388
  if (params.get('clientid') && !params.get('clientsecret') && !logout) {
382
389
  await addClientSecret();
383
390
  }
@@ -0,0 +1,81 @@
1
+ let cds;
2
+ const BaseCommand = require('./base_command');
3
+ const { CliError } = require('./helper/errors');
4
+ const { ParamCollection } = require('./params');
5
+ const AuthManager = require('./auth_manager');
6
+ const { capitalize } = require('./helper/string_helper');
7
+
8
+ module.exports = class ToggleSubscription extends BaseCommand {
9
+
10
+ /**
11
+ * @param on {boolean} whether to switch subscription on
12
+ */
13
+ constructor(on) {
14
+ super();
15
+ const prefix = on ? '' : 'un';
16
+ this.command = prefix + 'subscribe';
17
+ this.gerund = prefix + 'subscribing';
18
+ this.noun = prefix + 'subscription';
19
+ }
20
+
21
+ async run(paramValues, { tenant, local } = {}) {
22
+ cds = require('../cds');
23
+ let params = new ParamCollection(paramValues);
24
+ params.set('skipToken', true); // Token would have insufficient scopes for this command.
25
+ if (local) {
26
+ // Connect to locally-served MTXS
27
+ await this.runWithServer(params, tenant);
28
+ } else {
29
+ // Connect to running MTXS server
30
+ params = await AuthManager.login(params.toValueMap());
31
+ if (!params.has('username')) {
32
+ throw new CliError('Username is required if app URL is given.', { command: this.command });
33
+ }
34
+ await this.runClientOnly(params, tenant);
35
+ }
36
+ console.log(`${capitalize(this.noun)} successful.`);
37
+ }
38
+
39
+ async runClientOnly(params, tenant) {
40
+ console.log(capitalize(this.gerund), { tenant }, 'from', { url: params.get('appUrl') });
41
+ const url = `${params.get('appUrl')}/-/cds/deployment/${this.command}`;
42
+ try {
43
+ await require('axios').post(url, { tenant }, params.get('reqAuth'));
44
+ } catch (error) {
45
+ BaseCommand.handleHttpError(error, params, { url });
46
+ }
47
+ }
48
+
49
+ async runWithServer(params, tenant) {
50
+ if (!this.hasMtEnv()) {
51
+ throw new CliError('Local multitenant application environment with @sap/cds-mtxs module not found.', { command: this.command });
52
+ }
53
+
54
+ console.log(capitalize(this.gerund), { tenant });
55
+ const { 'cds.xt.DeploymentService': deploymentService } = await cds.serve([
56
+ '@sap/cds-mtxs/srv/deployment-service',
57
+ '@sap/cds-mtxs/srv/model-provider'
58
+ ]);
59
+ cds.emit('served');
60
+ if (this.command === 'unsubscribe') {
61
+ // MTXS doesn't connect automatically because unsubscribe requires no schema changes
62
+ await cds.connect.to('db');
63
+ }
64
+
65
+ await deploymentService[this.command](tenant);
66
+
67
+ if (cds.db) {
68
+ await cds.disconnect(cds.env.requires.multitenancy?.t0 ?? 't0'); // REVISIT: Ideally not necessary
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Check for application environment.
74
+ * Fails for edge cases (ignored for now):
75
+ * - @sap/cds-mtx application with missing modules (no npm install)
76
+ * - dev scenario with both @sap/cds-mtx and @sap/cds-mtxs installed
77
+ */
78
+ hasMtEnv() {
79
+ return cds.requires.multitenancy && !cds.mtx;
80
+ }
81
+ }
@@ -14,31 +14,37 @@ module.exports = function compile_to_xsuaa (model, options) {
14
14
  const scopes = {}
15
15
  const attributes = {}
16
16
 
17
- const findUserAttrInExpression = (expr, attributes = []) => {
18
- if (Array.isArray(expr)) { // descend arrays
19
- expr.forEach(e => findUserAttrInExpression(e, attributes))
20
- }
21
- else if (typeof expr === 'object' && expr.ref) {
22
- const userIdx = expr.ref.indexOf('$user')
23
- if (userIdx >= 0 && userIdx < expr.ref.length - 1) {
24
- const attr = expr.ref[userIdx + 1]
25
- if (!hardcoded.Attributes.includes(attr)) attributes.push(attr)
26
- }
27
- }
28
- return attributes
29
- }
30
-
31
17
  const parseAttributes = (condition) => { // e.g. 'foo = $user.bar or baz = $user.boo'
32
18
  if (!condition) return []
33
19
 
20
+ // first validate basic syntax of expression by parsing it
21
+ let expr
34
22
  try {
35
- // {"xpr":[{"ref":["foo"]},"=",{"ref":["$user","bar"]},"or",{"ref":["baz"]},"=",{"ref":["$user","boo"]}]}
36
- const expr = cds.parse.expr(condition)
37
- // find paths value following $user, i.e. 'bar' and 'boo'
38
- return findUserAttrInExpression(expr.xpr)
23
+ expr = cds.parse.expr(condition).xpr
39
24
  } catch (err) {
40
- throw new Error(`${err.message} in '${condition}'`)
25
+ throw new Error(`Invalid expression': ${err.message}`)
41
26
  }
27
+
28
+ // Reject expressions like `$user.foo.bar`, as xsuaa attributes are primitive, not structured
29
+ expr.forEach(expr => {
30
+ const userIdx = expr.ref?.indexOf('$user')
31
+ if (userIdx >= 0 && expr.ref.length > userIdx+2) {
32
+ throw new Error (`Structured attributes like '${expr.ref.join('.')}' are not supported`)
33
+ }
34
+ })
35
+
36
+ // Reject expressions like `$user.foo-bar`
37
+ // Impl. note: use the expression string rather than the parsed expression as the above example would be
38
+ // `[ { ref: [ '$user', 'foo' ] }, '-', { ref: [ 'bar' ] } ]`
39
+ // making it ambiguous to tell if it was `$user.foo-bar` or `$user.foo - bar`
40
+ return Array.from(condition.matchAll(/\$user\s*\.\s*(\S*)/g))
41
+ .map(m => m[1])
42
+ .filter(m => !hardcoded.Attributes.includes(m))
43
+ .map(m => {
44
+ // see https://help.sap.com/docs/CP_AUTHORIZ_TRUST_MNG/ae8e8427ecdf407790d96dad93b5f723/517895a9612241259d6941dbf9ad81cb.html#attributes
45
+ if (!/^[a-zA-Z0-9_]*$/.test(m)) throw new Error(`Illegal attribute name '${m}' in '${condition}'. Only characters a-z, A-Z, 0-9, _ are allowed.`)
46
+ return m
47
+ })
42
48
  }
43
49
 
44
50
  cds.reflect(model).foreach(def => {
@@ -446,7 +446,7 @@ class Expression {
446
446
  if (args.includes('$IsFlags') && attribs.IsFlags === 'true') target.$IsFlags = true;
447
447
  if (args.includes('$Alias') && attribs.Alias) target.$Alias = attribs.Alias;
448
448
  if (args.includes('$Namespace') && attribs.Namespace) target.$Namespace = attribs.Namespace;
449
-
449
+ if (args.includes('$OpenType') && attribs.OpenType) target.$OpenType = attribs.OpenType;
450
450
  if (args.includes('$BaseType') && attribs.BaseType) target.$BaseType = attribs.BaseType;
451
451
  if (args.includes('$MaxLength') && attribs.MaxLength != null && attribs.MaxLength.toLowerCase() !== 'max') {
452
452
  target.$MaxLength = parseInt(attribs.MaxLength, 10);
@@ -2003,7 +2003,7 @@ class EntityTypeExpression extends AbstractTypeExpression {
2003
2003
  constructor(element, expression, context) { super(element, expression, context, 'EntityType'); }
2004
2004
  interpret() {
2005
2005
  const result = super.interpret();
2006
- this.buildTypeCollectionAndFacets('$Type', '$BaseType', '$HasStream');
2006
+ this.buildTypeCollectionAndFacets('$Type', '$BaseType', '$HasStream', '$OpenType');
2007
2007
  return result;
2008
2008
  }
2009
2009
  }
@@ -2012,7 +2012,7 @@ class ComplexTypeExpression extends AbstractTypeExpression {
2012
2012
  constructor(element, expression, context) { super(element, expression, context, 'ComplexType'); }
2013
2013
  interpret() {
2014
2014
  const result = super.interpret();
2015
- this.buildTypeCollectionAndFacets('$Type', '$BaseType', '$HasStream');
2015
+ this.buildTypeCollectionAndFacets('$Type', '$BaseType', '$HasStream', '$OpenType');
2016
2016
  return result;
2017
2017
  }
2018
2018
  }
@@ -54,6 +54,7 @@ function _initialize(parserContext) {
54
54
  parserContext.allComplexTypeDocs = {};
55
55
  parserContext.allComplexTypeNamespaces = {};
56
56
  parserContext.allInheritedComplexTypes = {};
57
+ parserContext.allInheritedEntityTypes = {};
57
58
  parserContext.allowedNamespaces = [];
58
59
  parserContext.allowAllNamespaces = false;
59
60
  }
@@ -339,13 +340,16 @@ function _extractComplexType(complexType, parserContext) {
339
340
  complexTypeKey = parserContext.serviceNamespace + "." + complexType[key].Name;
340
341
  namespaceAttributes = namespaceAttributeFilter(complexType[key], parserContext);
341
342
  baseType = complexType[key].BaseType;
343
+ if (complexType[key].Abstract || complexType[key].OpenType) {
344
+ complexType.open = true;
345
+ }
342
346
  } else if (key === "Property") {
343
347
  properties = complexType.Property;
344
348
  } else if (key === "Documentation") {
345
349
  parserContext.allComplexTypeDocs[complexTypeKey] = _parseDocumentationTag(complexType.Documentation);
346
350
  }
347
351
  });
348
- parserContext.allComplexTypes[complexTypeKey] = properties;
352
+ parserContext.allComplexTypes[complexTypeKey] = complexType;
349
353
  if (complexTypeKey && baseType) {
350
354
  parserContext.allInheritedComplexTypes[complexTypeKey] = baseType;
351
355
  }
@@ -381,7 +385,7 @@ function _getServiceEntityProperty(
381
385
  ) {
382
386
  let isCollection = -1;
383
387
  //preprocessing of datatype in case it is a collection
384
- if (dataType.substring(0, 10) === 'Collection') {
388
+ if (dataType && dataType.substring(0, 10) === 'Collection') {
385
389
  dataType = dataType.substring(11, dataType.length - 1);
386
390
  isCollection = 1;
387
391
  }
@@ -483,15 +487,11 @@ function _getServiceEntityProperty(
483
487
  propertyJson = propertyJson + ',\n"scale":' + 0;
484
488
  }
485
489
  }
486
- if (nullable == "false") {
490
+ if (nullable === "false")
487
491
  propertyJson = propertyJson + ',\n"notNull": true';
488
- }
489
- else if(nullable=="true" || !nullable){
490
- propertyJson = propertyJson + ',\n"notNull": false';
491
- }
492
492
  propertyJson = propertyJson + '\n}'
493
493
  }
494
- else{
494
+ else {
495
495
  propertyJson = propertyJson + '"type":"' + cdsDataType + '"';
496
496
  //if annotations to be added
497
497
  if (edmxncdsdatatype[dataType + "_a"] && cdsDataType != "cds.Date") {
@@ -528,13 +528,8 @@ function _getServiceEntityProperty(
528
528
  propertyJson += _generateNamespaceAttributes(allowedNamespaceAttributes, true);
529
529
  }
530
530
 
531
- if(isCollection == -1) {
532
- if (nullable === "false") {
533
- propertyJson = propertyJson + ',\n"notNull": true';
534
- }
535
- else if(nullable=="true" || !nullable){
536
- propertyJson = propertyJson + ',\n"notNull": false';
537
- }
531
+ if (isCollection === -1 && nullable === "false") {
532
+ propertyJson = propertyJson + ',\n"notNull": true';
538
533
  }
539
534
 
540
535
  if (propertyName)
@@ -542,11 +537,14 @@ function _getServiceEntityProperty(
542
537
  return propertyJson;
543
538
  }
544
539
 
545
- function _getServiceComplexType(complexTypeKey, complexType, parserContext) {
540
+ function _getServiceComplexType(complexTypeKey, complexType, parserContext, isOpenType) {
546
541
  let complexTypeCSN = '"' + complexTypeKey + '": {\n';
547
542
  let complexTypeProperty;
548
- let i;
549
543
  complexTypeCSN = complexTypeCSN + '"kind": "type",\n';
544
+ // checking if the ComplexType is marked open or acts as a BaseType
545
+ if (isOpenType === true || Object.values(parserContext.allInheritedComplexTypes).indexOf(complexTypeKey) > -1) {
546
+ complexTypeCSN = complexTypeCSN + '"@open": true,\n';
547
+ }
550
548
  complexTypeCSN = complexTypeCSN + '"@cds.external": true,\n';
551
549
  if (parserContext.allComplexTypeNamespaces[complexTypeKey]) {
552
550
  complexTypeCSN += _generateNamespaceAttributes(parserContext.allComplexTypeNamespaces[complexTypeKey], false);
@@ -557,14 +555,14 @@ function _getServiceComplexType(complexTypeKey, complexType, parserContext) {
557
555
  if (complexType) {
558
556
  if (complexType.length) {
559
557
  // More than one complex types
560
- for (i = 0; i < complexType.length; i++) {
558
+ for (let i = 0; i < complexType.length; i++) {
561
559
  complexTypeProperty = complexType[i]._attributes;
562
560
  if (complexType[i].Documentation)
563
561
  complexTypeProperty.doc = _parseDocumentationTag(complexType[i].Documentation);
564
562
  let namespaceAttributes = namespaceAttributeFilter(complexTypeProperty, parserContext);
565
563
  const complexTypePropertyNamespace = namespaceAttributes.length ? namespaceAttributes : -1
566
564
 
567
- let complexProperty =
565
+ let complexProperty =
568
566
  _getServiceEntityProperty(
569
567
  complexTypeProperty.Name,
570
568
  complexTypeProperty.Type,
@@ -617,7 +615,7 @@ function _getServiceComplexType(complexTypeKey, complexType, parserContext) {
617
615
  complexTypeCSN = complexTypeCSN + "}\n";
618
616
  if (parserContext.allInheritedComplexTypes[complexTypeKey]) {
619
617
  complexTypeCSN = complexTypeCSN + ",\n";
620
- complexTypeCSN = complexTypeCSN +
618
+ complexTypeCSN = complexTypeCSN +
621
619
  '"includes": ["' + parserContext.allInheritedComplexTypes[complexTypeKey] + '"]\n';
622
620
  }
623
621
  complexTypeCSN = complexTypeCSN + "}\n";
@@ -629,10 +627,12 @@ function _getServiceComplexTypes(parserContext) {
629
627
  let complexTypesKeys = Object.keys(parserContext.allComplexTypes);
630
628
  let complexTypeKey;
631
629
  let complexType;
630
+ let isOpenType;
632
631
  for (let i = 0; i < complexTypesKeys.length; i++) {
633
632
  complexTypeKey = complexTypesKeys[i];
634
- complexType = parserContext.allComplexTypes[complexTypesKeys[i]];
635
- complexTypeCSN = complexTypeCSN + _getServiceComplexType(complexTypeKey, complexType, parserContext);
633
+ complexType = parserContext.allComplexTypes[complexTypeKey].Property;
634
+ isOpenType = parserContext.allComplexTypes[complexTypeKey].open;
635
+ complexTypeCSN = complexTypeCSN + _getServiceComplexType(complexTypeKey, complexType, parserContext, isOpenType);
636
636
  if (i !== complexTypesKeys.length - 1) {
637
637
  complexTypeCSN = complexTypeCSN + ",\n";
638
638
  } else {
@@ -787,6 +787,7 @@ function _parseEntityAttributes(attributes, parserContext) {
787
787
  entityAttributes.Name = attributes.Name;
788
788
  entityAttributes.BaseType = attributes.BaseType;
789
789
  entityAttributes.Abstract = attributes.Abstract;
790
+ entityAttributes.OpenType = attributes.OpenType;
790
791
  entityAttributes.allowedNamespaceAttributes = namespaceAttributeFilter(attributes, parserContext);
791
792
 
792
793
  return entityAttributes;
@@ -1304,6 +1305,11 @@ function _getServiceEntityNavigationProperty(navPropAttributes, mockServerUc, pa
1304
1305
  }
1305
1306
  // annotation for on conditions
1306
1307
  csn = csn + `"@cds.ambiguous": "missing on condition?",\n`;
1308
+
1309
+ //Code for notNull working but change is not reflecting when we use: cds compile {file-path} -2 edmx > {newFileName}
1310
+ /*if (navPropAttributes.Nullable === "false")
1311
+ csn = csn + '"notNull": true,\n';*/
1312
+
1307
1313
  // if documentation exists for navigation property
1308
1314
  if (navPropDoc)
1309
1315
  csn = csn + '"doc":' + ` "${navPropDoc}",\n`;
@@ -1396,8 +1402,8 @@ function _constructServiceEntity(
1396
1402
  csnEntity = csnEntity + '"@cds.persistence.skip": true,\n';
1397
1403
  }
1398
1404
 
1399
- if (entityAttributes.Abstract && entityAttributes.Abstract.toUpperCase() === "TRUE") {
1400
- csnEntity = csnEntity + '"abstract": true,\n';
1405
+ if ((entityAttributes.Abstract && entityAttributes.Abstract.toUpperCase() === "TRUE") || (entityAttributes.OpenType && entityAttributes.OpenType.toUpperCase() === "TRUE")) {
1406
+ csnEntity = csnEntity + '"@open": true,\n'
1401
1407
  }
1402
1408
 
1403
1409
  /* Merging the namespace list from entity set and entity type
@@ -1525,6 +1531,7 @@ function _parseServiceEntity(entityName, entity, ignorePersistenceSkip, mockServ
1525
1531
  // If inherited entity found
1526
1532
  if (entityAttributes.BaseType) {
1527
1533
  parserContext.allEntities.push(_extractEntityFromNamespace(entityAttributes.BaseType));
1534
+ parserContext.allInheritedEntityTypes[_extractEntityFromNamespace(entityAttributes.BaseType)] = entity["_attributes"].Name;
1528
1535
  }
1529
1536
  // adding blob element if entity has m:HasStream as true
1530
1537
  const checkProperty = entityAttributes["allowedNamespaceAttributes"].filter(e => e["m.HasStream"] === 'true');
@@ -1614,6 +1621,12 @@ function _getServiceEntitites(entityJson = [], ignorePersistenceSkip, mockServer
1614
1621
  if (entitiesWithNames[i].length) {
1615
1622
  parsedEntities[i] = _parsingServiceEntities(entitiesWithNames[i],
1616
1623
  ignorePersistenceSkip, mockServerUc, parserContext);
1624
+ // adding @open annotation to BaseTypes
1625
+ for (let j = 0; j < parsedEntities[i].length; j++) {
1626
+ if (parserContext.allInheritedEntityTypes[_extractEntityFromNamespace(parserContext.allEntitySetMap[entitiesWithNames[i][j].name])] != undefined || parserContext.allInheritedEntityTypes[entitiesWithNames[i][j].name]) {
1627
+ parsedEntities[i][j] = parsedEntities[i][j].replace(/}$/, ',"@open": true' + '}');
1628
+ }
1629
+ }
1617
1630
  }
1618
1631
  }
1619
1632
  // if entities from entity set and entity type both are parsed