@sap/cds-dk 7.1.0 → 7.2.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 (56) hide show
  1. package/bin/build.js +3 -3
  2. package/bin/cds.js +2 -1
  3. package/bin/compile/index.js +5 -1
  4. package/bin/gen.js +27 -0
  5. package/bin/help.js +2 -1
  6. package/bin/watch.js +3 -4
  7. package/bin/watched.js +2 -3
  8. package/lib/bind/bindingUtil.js +1 -0
  9. package/lib/build/buildTaskEngine.js +13 -17
  10. package/lib/build/buildTaskFactory.js +23 -27
  11. package/lib/build/buildTaskHandler.js +6 -12
  12. package/lib/build/buildTaskProviderFactory.js +9 -12
  13. package/lib/build/constants.js +2 -1
  14. package/lib/build/index.js +1 -1
  15. package/lib/build/provider/buildTaskHandlerInternal.js +2 -2
  16. package/lib/build/provider/buildTaskProviderInternal.js +15 -25
  17. package/lib/build/provider/fiori/index.js +4 -3
  18. package/lib/build/provider/hana/index.js +14 -7
  19. package/lib/build/provider/java/index.js +2 -2
  20. package/lib/build/provider/nodejs/index.js +51 -18
  21. package/lib/build/util.js +11 -19
  22. package/lib/compile/openapi/csdl2openapi.js +30 -21
  23. package/lib/compile/openapi/index.js +7 -3
  24. package/lib/import/common.js +1 -1
  25. package/lib/import/message.js +6 -1
  26. package/lib/import/odata/metadataconverter/MetadataConverter.js +13 -9
  27. package/lib/import/odata/v4parser.js +26 -12
  28. package/lib/init/constants.js +6 -11
  29. package/lib/init/index.js +84 -71
  30. package/lib/init/template/_merging/registry-mta.js +0 -12
  31. package/lib/init/template/approuter/files/mta.yaml.hbs +1 -1
  32. package/lib/init/template/approuter/index.js +7 -6
  33. package/lib/init/template/cf-manifest/files/services-manifest.yml.hbs +0 -5
  34. package/lib/init/template/cf-manifest/index.js +2 -2
  35. package/lib/init/template/h2/index.js +16 -0
  36. package/lib/init/template/hana/index.js +2 -3
  37. package/lib/init/template/helm/chart/values.root.schema.json +0 -5
  38. package/lib/init/template/helm/chart/values.schema.json +0 -4
  39. package/lib/init/template/helm/index.js +1 -1
  40. package/lib/init/template/lint/index.js +3 -7
  41. package/lib/init/template/multitenancy/files/package.json.hbs +0 -3
  42. package/lib/init/template/multitenancy/index.js +2 -0
  43. package/lib/init/template/nodejs/index.js +3 -3
  44. package/lib/init/template/pipeline/index.js +2 -4
  45. package/lib/init/template/sample/index.js +5 -7
  46. package/lib/init/template/sqlite/index.js +8 -3
  47. package/lib/init/template/tiny-sample/index.js +16 -30
  48. package/lib/init/util/mvn.js +25 -1
  49. package/lib/init/util/projectReader.js +3 -7
  50. package/lib/init/util/template.js +7 -6
  51. package/lib/watch/node-watch.js +6 -1
  52. package/npm-shrinkwrap.json +46 -84
  53. package/package.json +5 -4
  54. package/lib/init/template/auditlog/files/mta.yaml.hbs +0 -14
  55. package/lib/init/template/auditlog/files/package.json.hbs +0 -27
  56. package/lib/init/template/auditlog/index.js +0 -74
@@ -2,7 +2,7 @@ const fs = require('fs')
2
2
  const path = require('path')
3
3
  const cds = require('../../../cds')
4
4
  const BuildTaskHandlerEdmx = require('../buildTaskHandlerEdmx')
5
- const { OUTPUT_MODE, OUTPUT_MODE_RESULT, FILE_EXT_CDS, CONTENT_LANGUAGE_BUNDLES, CONTENT_DEFAULT_CSN, DEFAULT_CSN_FILE_NAME } = require('../../constants')
5
+ const { OUTPUT_MODE, OUTPUT_MODE_RESULT, FILE_EXT_CDS, CONTENT_LANGUAGE_BUNDLES, CONTENT_DEFAULT_CSN, DEFAULT_CSN_FILE_NAME, OUTPUT_MODE_FILESYSTEM } = require('../../constants')
6
6
  const { getI18nDefaultFolder } = require('../../util')
7
7
  const DEFAULT_COMPILE_DEST_FOLDER = path.normalize('src/main/resources/edmx')
8
8
 
@@ -34,7 +34,7 @@ class JavaModuleBuilder extends BuildTaskHandlerEdmx {
34
34
  this._result.languageBundles = i18n.bundles
35
35
  }
36
36
  }
37
- if (this.isStagingBuild() && this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
37
+ if (this.isStagingBuild() && this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM)) {
38
38
  await this._copyNativeContent(src, dest)
39
39
  }
40
40
  return this._result
@@ -52,31 +52,64 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
52
52
  if (this.isStagingBuild() && this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM)) {
53
53
  const srcSrv = this.task.src === cds.root ? path.resolve(this.task.src, cds.env.folders.srv) : this.task.src
54
54
  await this._copyNativeContent(cds.root, srcSrv, destRoot, destSrv)
55
- await this._fixCapireSamplesDeps(destRoot)
55
+ if (this.context.options.workspaces) {
56
+ await this._addWorkspaceDependencies(destRoot)
57
+ }
56
58
  }
57
59
  return this._result
58
60
  }
59
61
 
60
- // REVISIT: fix for capire samples - needs to be reworked
61
- // Instead of adding relative paths we may better delete the entire entry as relative paths
62
- // cannot be resolved in cloud deployments anyway.
62
+ // REVISIT: fix for simple mono repos using npm workspaces
63
63
  // Support for HANA native artifacts defined in sub-modules required
64
- async _fixCapireSamplesDeps(dest) {
65
- const packageJson = path.join(dest, 'package.json')
64
+ async _addWorkspaceDependencies(dest) {
66
65
  try {
67
- const content = require(packageJson)
68
- const { dependencies } = content
69
- let changed
70
- for (let dependency in dependencies) {
71
- if (dependency.startsWith('@capire/')) {
72
- dependencies[dependency] = path.relative(dest, path.dirname(require.resolve(dependency + '/package.json')))
73
- changed = true
74
- }
66
+ const workspaceRoot = path.resolve(cds.root, this.getBuildOption("workspaceRoot") ?? '.')
67
+ if (workspaceRoot !== cds.root && !fs.existsSync(workspaceRoot)) {
68
+ throw new BuildError(`The given workspaceRoot path ${workspaceRoot} does not exist`)
75
69
  }
76
- if (changed) {
77
- return this.write(JSON.stringify(content, null, ' ')).to(packageJson)
70
+ const packageJson = require(path.join(dest, 'package.json'))
71
+ const workspaces = workspaceRoot === cds.root ? packageJson.workspaces : require(path.join(workspaceRoot, 'package.json')).workspaces
72
+ if (workspaces?.length) {
73
+ this.logger._debug && this.logger.debug(`Existing npm workspace configuration '${JSON.stringify(workspaces, ' ')}'`)
74
+ const { tar } = require('../../../../lib').utils
75
+ const { dependencies } = packageJson
76
+ const folders = fs.readdirSync(workspaceRoot).filter(res => {
77
+ try {
78
+ return fs.lstatSync(path.join(workspaceRoot, res)).isDirectory()
79
+ } catch (e) {
80
+ return false
81
+ }
82
+ })
83
+ this.logger._debug && this.logger.debug(`Existing workspace subfolders '${JSON.stringify(folders, ' ')}'`)
84
+
85
+ const workspaceNames = new Set()
86
+ let changed
87
+ for (const workspace of workspaces) {
88
+ //REVISIT: workspaces must only use simple glob like 'foo*', nested folders are not supported
89
+ const regex = new RegExp('^' + workspace.split('/').pop().replace(/\*/g, '.*') + '$')
90
+ for (const folder of folders) {
91
+ if (!workspaceNames.has(folder) && folder.match(regex)) {
92
+ workspaceNames.add(folder)
93
+ const plugin = require(require.resolve(path.join(workspaceRoot, folder, 'package.json')))
94
+ const tarFile = `${plugin.name.replace(/\//g,'-').replace(/@/, '')}-${plugin.version}.tgz`
95
+ if (dependencies[plugin.name] === '*') {
96
+ const tarFilePath = path.join(dest, tarFile)
97
+ dependencies[plugin.name] = `file:${tarFile}`
98
+ await tar.czfd(tarFilePath, path.join(workspaceRoot, folder))
99
+ this.pushFile(tarFilePath)
100
+ changed = true
101
+ }
102
+ }
103
+ }
104
+ }
105
+ if (changed) {
106
+ return this.write(JSON.stringify(packageJson, null, ' ')).to(path.join(dest, 'package.json'))
107
+ }
78
108
  }
79
- } catch (e) {/*ignore*/ }
109
+ } catch (e) {
110
+ this.pushMessage(`Failed to add npm workspace dependencies: ${e.message}`)
111
+ this.logger.debug(e)
112
+ }
80
113
  }
81
114
 
82
115
  async clean() {
@@ -91,7 +124,7 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
91
124
 
92
125
  async _copyNativeContent(srcRoot, srcSrv, destRoot, destSrv) {
93
126
  // project/srv/** -> 'gen/srv/srv/**'
94
- const filesFilter = await this.copySrvContent(srcSrv, destRoot, this.hasBuildOption('flatDeployLayout', true)? destRoot : destSrv)
127
+ const filesFilter = await this.copySrvContent(srcSrv, destRoot, this.hasBuildOption('flatDeployLayout', true) ? destRoot : destSrv)
95
128
 
96
129
  // project/* -> 'gen/srv/*'
97
130
  await this.copyProjectRootContent(srcRoot, destRoot, (entry) => !filesFilter.includes(path.basename(entry)))
package/lib/build/util.js CHANGED
@@ -119,6 +119,14 @@ function getDefaultModelOptions() {
119
119
  return _pushModelPaths(modelPaths)
120
120
  }
121
121
 
122
+ /**
123
+ * For valid paths replace '\\' with '/' and remove trailing '/'. Otherwise return as is
124
+ * @param {*} dir
125
+ */
126
+ function normalizePath(dir) {
127
+ return typeof dir === "string" ? dir.replace(/\\/g, '/').replace(/\/$/, '') : dir
128
+ }
129
+
122
130
  function _pushModelPaths(...modelPaths) {
123
131
  const model = new Set()
124
132
  // may contain nested arrays
@@ -131,7 +139,7 @@ function _pushModelPaths(...modelPaths) {
131
139
  if (roots.includes(m)) {
132
140
  const dir = path.resolve(cds.root, m)
133
141
  if (fs.existsSync(dir)) {
134
- model.add(m.replace(/\/$/, ''))
142
+ model.add(m)
135
143
  } else if (fs.existsSync(dir + FILE_EXT_CDS)) { //might be cds file name, compatibility to old build configs
136
144
  model.add(m)
137
145
  }
@@ -140,7 +148,7 @@ function _pushModelPaths(...modelPaths) {
140
148
  }
141
149
  }
142
150
  })
143
- return [...model]
151
+ return [...model].map(m => normalizePath(m))
144
152
  }
145
153
 
146
154
  function flatten(modelPaths) {
@@ -154,22 +162,6 @@ function flatten(modelPaths) {
154
162
  }, [])
155
163
  }
156
164
 
157
- /**
158
- * Copy a file or directory. The directory can have contents.
159
- * REVISIT: 'fs.promises.cp' replacement for nodejs 14
160
- * @param src <String> Note that if src is a directory it will copy everything inside of this directory, not the entire directory itself.
161
- * @param dest <String> Note that if src is a file, dest cannot be a directory.
162
- */
163
- async function copy(src, dest) {
164
- if ((await fs.promises.stat(src)).isDirectory()) {
165
- const entries = await fs.promises.readdir(src)
166
- return Promise.all(entries.map(async each => copy(path.join(src, each), path.join(dest, each))))
167
- } else {
168
- await fs.promises.mkdir(path.dirname(dest), { recursive: true })
169
- return fs.promises.copyFile(src, dest)
170
- }
171
- }
172
-
173
165
  function getI18nDefaultFolder() {
174
166
  // keep '_i18n' as preferred folder for compatibility reasons
175
167
  if (cds.env.i18n.folders.includes('_i18n')) return '_i18n'
@@ -276,9 +268,9 @@ module.exports = {
276
268
  resolveRequiredSapModels,
277
269
  getDefaultModelOptions,
278
270
  flatten,
279
- copy,
280
271
  getI18nDefaultFolder,
281
272
  isClassicMtx,
273
+ normalizePath,
282
274
  BuildMessage,
283
275
  BuildError
284
276
  }
@@ -52,6 +52,7 @@ module.exports.csdl2openapi = function (
52
52
  {
53
53
  url: serviceRoot,
54
54
  servers: serversObject,
55
+ odataVersion: odataVersion,
55
56
  scheme: scheme = 'https',
56
57
  host: host = 'localhost',
57
58
  basePath: basePath = '/service-root',
@@ -61,6 +62,7 @@ module.exports.csdl2openapi = function (
61
62
  ) {
62
63
  // as preProcess below mutates the csdl, copy it before, to avoid side-effects on the caller side
63
64
  csdl = JSON.parse(JSON.stringify(csdl))
65
+ csdl.$Version = odataVersion ? odataVersion : '4.01'
64
66
  serviceRoot = serviceRoot || (scheme + '://' + host + basePath);
65
67
  const queryOptionPrefix = csdl.$Version <= '4.0' ? '$' : '';
66
68
  const typesToInline = {}; // filled in schema() and used in inlineTypes()
@@ -82,7 +84,7 @@ module.exports.csdl2openapi = function (
82
84
  openapi: '3.0.2',
83
85
  info: getInfo(csdl, entityContainer),
84
86
  'x-sap-api-type': 'ODATAV4',
85
- 'x-odata-version': '4.0',
87
+ 'x-odata-version': csdl.$Version,
86
88
  'x-sap-shortText': getShortText(csdl, entityContainer),
87
89
  servers: getServers(serviceRoot, serversObject),
88
90
  tags: entityContainer ? getTags(entityContainer) : {},
@@ -638,12 +640,12 @@ module.exports.csdl2openapi = function (
638
640
  */
639
641
  function operationCreate(pathItem, element, name, sourceName, targetName, target, level, restrictions) {
640
642
  const insertRestrictions = restrictions.InsertRestrictions || target && target[voc.Capabilities.InsertRestrictions] || {};
641
-
643
+ let countRestrictions = target && (target[voc.Capabilities.CountRestrictions]?.Countable === false); // count property will be added if CountRestrictions is false
642
644
  if (insertRestrictions.Insertable !== false) {
643
645
  const lname = pluralize.singular(splitName(name));
644
646
  const type = modelElement(element.$Type);
645
647
  pathItem.post = {
646
- summary: insertRestrictions.Description || operationSummary('Create', name, sourceName, level, true, true),
648
+ summary: insertRestrictions.Description || operationSummary('Creates', name, sourceName, level, true, true),
647
649
  tags: [sourceName],
648
650
  requestBody: {
649
651
  description: type && type[voc.Core.Description] || 'New ' + lname,
@@ -653,8 +655,8 @@ module.exports.csdl2openapi = function (
653
655
  schema: ref(element.$Type, SUFFIX.create),
654
656
  }
655
657
  }
656
- },
657
- responses: response(201, 'Created ' + lname, { $Type: element.$Type }, insertRestrictions.ErrorResponses),
658
+ },
659
+ responses: response(201, 'Created ' + lname, { $Type: element.$Type }, insertRestrictions.ErrorResponses, !countRestrictions),
658
660
  };
659
661
  if (insertRestrictions.LongDescription) pathItem.post.description = insertRestrictions.LongDescription;
660
662
  if (targetName && sourceName != targetName) pathItem.post.tags.push(targetName);
@@ -711,6 +713,7 @@ module.exports.csdl2openapi = function (
711
713
  const readRestrictions = restrictions.ReadRestrictions || targetRestrictions || {};
712
714
  const readByKeyRestrictions = readRestrictions.ReadByKeyRestrictions;
713
715
  let readable = true;
716
+ let countRestrictions = target && (target[voc.Capabilities.CountRestrictions]?.Countable === false);
714
717
  if (byKey && readByKeyRestrictions && readByKeyRestrictions.Readable !== undefined)
715
718
  readable = readByKeyRestrictions.Readable;
716
719
  else if (readRestrictions.Readable !== undefined)
@@ -722,11 +725,11 @@ module.exports.csdl2openapi = function (
722
725
  const lname = splitName(name);
723
726
  const collection = !byKey && element.$Collection;
724
727
  const operation = {
725
- summary: descriptions.Description || operationSummary('Retrieve', name, sourceName, level, element.$Collection, byKey),
728
+ summary: descriptions.Description || operationSummary('Retrieves', name, sourceName, level, element.$Collection, byKey),
726
729
  tags: [sourceName],
727
730
  parameters: [],
728
731
  responses: response(200, 'Retrieved ' + (byKey ? pluralize.singular(lname) : lname), { $Type: element.$Type, $Collection: collection },
729
- byKey ? readByKeyRestrictions?.ErrorResponses : readRestrictions?.ErrorResponses)
732
+ byKey ? readByKeyRestrictions?.ErrorResponses : readRestrictions?.ErrorResponses, !countRestrictions)
730
733
  };
731
734
  const deltaSupported = element[voc.Capabilities.ChangeTracking] && element[voc.Capabilities.ChangeTracking].Supported;
732
735
  if (!byKey && deltaSupported) {
@@ -1165,11 +1168,11 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
1165
1168
  */
1166
1169
  function operationUpdate(pathItem, element, name, sourceName, target, level, restrictions, byKey) {
1167
1170
  const updateRestrictions = restrictions.UpdateRestrictions || target && target[voc.Capabilities.UpdateRestrictions] || {};
1168
-
1171
+ let countRestrictions = target && (target[voc.Capabilities.CountRestrictions]?.Countable === false);
1169
1172
  if (updateRestrictions.Updatable !== false) {
1170
1173
  const type = modelElement(element.$Type);
1171
1174
  const operation = {
1172
- summary: updateRestrictions.Description || operationSummary('Change', name, sourceName, level, element.$Collection, byKey),
1175
+ summary: updateRestrictions.Description || operationSummary('Changes', name, sourceName, level, element.$Collection, byKey),
1173
1176
  tags: [sourceName],
1174
1177
  requestBody: {
1175
1178
  description: type && type[voc.Core.Description] || 'New property values',
@@ -1180,7 +1183,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
1180
1183
  }
1181
1184
  }
1182
1185
  },
1183
- responses: response(204, "Success", undefined, updateRestrictions.ErrorResponses),
1186
+ responses: response(204, "Success", undefined, updateRestrictions.ErrorResponses, !countRestrictions),
1184
1187
  };
1185
1188
  if (updateRestrictions.LongDescription) operation.description = updateRestrictions.LongDescription;
1186
1189
  customParameters(operation, updateRestrictions);
@@ -1202,12 +1205,12 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
1202
1205
  */
1203
1206
  function operationDelete(pathItem, element, name, sourceName, target, level, restrictions, byKey) {
1204
1207
  const deleteRestrictions = restrictions.DeleteRestrictions || target && target[voc.Capabilities.DeleteRestrictions] || {};
1205
-
1208
+ let countRestrictions = target && (target[voc.Capabilities.CountRestrictions]?.Countable === false);
1206
1209
  if (deleteRestrictions.Deletable !== false) {
1207
1210
  pathItem.delete = {
1208
- summary: deleteRestrictions.Description || operationSummary('Delete', name, sourceName, level, element.$Collection, byKey),
1211
+ summary: deleteRestrictions.Description || operationSummary('Deletes', name, sourceName, level, element.$Collection, byKey),
1209
1212
  tags: [sourceName],
1210
- responses: response(204, "Success", undefined, deleteRestrictions.ErrorResponses),
1213
+ responses: response(204, "Success", undefined, deleteRestrictions.ErrorResponses, !countRestrictions),
1211
1214
  };
1212
1215
  if (deleteRestrictions.LongDescription) pathItem.delete.description = deleteRestrictions.LongDescription;
1213
1216
  customParameters(pathItem.delete, deleteRestrictions);
@@ -1423,7 +1426,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
1423
1426
  const name = actionName.indexOf('.') === -1 ? actionName : nameParts(actionName).name;
1424
1427
  const pathItem = {
1425
1428
  post: {
1426
- summary: actionImport[voc.Core.Description] || overload[voc.Core.Description] || 'Invoke action ' + name,
1429
+ summary: actionImport[voc.Core.Description] || overload[voc.Core.Description] || 'Invokes action ' + name,
1427
1430
  tags: [overload[voc.Common.Label] || sourceName || 'Service Operations'],
1428
1431
  responses: overload.$ReturnType ? response(200, "Success", overload.$ReturnType, overload[voc.Capabilities.OperationRestrictions]?.ErrorResponses)
1429
1432
  : response(204, "Success", undefined, overload[voc.Capabilities.OperationRestrictions]?.ErrorResponses),
@@ -1479,7 +1482,6 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
1479
1482
  const name = functionName.indexOf('.') === -1 ? functionName : nameParts(functionName).name;
1480
1483
  let parameters = overload.$Parameter || [];
1481
1484
  if (overload.$IsBound) parameters = parameters.slice(1);
1482
-
1483
1485
  const pathSegments = [];
1484
1486
  const params = [];
1485
1487
 
@@ -1534,7 +1536,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
1534
1536
  const pathParameters = implicitAliases ? '' : '(' + pathSegments.join(',') + ')';
1535
1537
  const pathItem = {
1536
1538
  get: {
1537
- summary: functionImport[voc.Core.Description] || overload[voc.Core.Description] || 'Invoke function ' + name,
1539
+ summary: functionImport[voc.Core.Description] || overload[voc.Core.Description] || 'Invokes function ' + name,
1538
1540
  tags: [overload[voc.Common.Label] || sourceName || 'Service Operations'],
1539
1541
  parameters: prefixParameters.concat(params),
1540
1542
  responses: response(200, "Success", overload.$ReturnType, overload[voc.Capabilities.OperationRestrictions]?.ErrorResponses),
@@ -1558,7 +1560,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
1558
1560
  const firstEntitySet = Object.keys(container).filter(child => isIdentifier(child) && container[child].$Collection)[0];
1559
1561
  paths['/$batch'] = {
1560
1562
  post: {
1561
- summary: batchSupport[voc.Core.Description] || 'Send a group of requests',
1563
+ summary: batchSupport[voc.Core.Description] || 'Sends a group of requests',
1562
1564
  description: (batchSupport[voc.Core.LongDescription] || 'Group multiple requests into a single request payload, see '
1563
1565
  + '[Batch Requests](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_BatchRequests).')
1564
1566
  + '\n\n*Please note that "Try it out" is not supported for this request.*',
@@ -1613,11 +1615,12 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
1613
1615
  * @param {object} type Response type object
1614
1616
  * @param {array} errors Array of operation-specific status codes with descriptions
1615
1617
  */
1616
- function response(code, description, type, errors) {
1618
+ function response(code, description, type, errors, isCount = true) {
1617
1619
  const r = {};
1618
1620
  r[code] = {
1619
1621
  description: description
1620
1622
  };
1623
+ let CountPropertyObj = {[csdl.$Version > '4.0' ? '@count' : '@odata.count'] : ref('count')} ;
1621
1624
  if (code != 204) {
1622
1625
  const s = getSchema(type);
1623
1626
  r[code].content = {
@@ -1627,7 +1630,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
1627
1630
  type: 'object',
1628
1631
  title: 'Collection of ' + nameParts(type.$Type || 'Edm.String').name,
1629
1632
  properties: {
1630
- [csdl.$Version > '4.0' ? '@count' : '@odata.count']: ref('count'),
1633
+ ...(isCount && CountPropertyObj),
1631
1634
  value: s
1632
1635
  }
1633
1636
  }
@@ -1806,14 +1809,20 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
1806
1809
  const isKey = keyMap(type);
1807
1810
  const required = Object.keys(isKey);
1808
1811
  const schemaProperties = {};
1809
-
1812
+ let isCount = true;
1813
+ if (csdl[qualifier]?.$Annotations) {
1814
+ const annotations = csdl[qualifier].$Annotations[`${qualifier}.EntityContainer/${name}`];
1815
+ if (annotations && annotations[voc.Capabilities.CountRestrictions] && annotations[voc.Capabilities.CountRestrictions]?.Countable === false) {
1816
+ isCount = false;
1817
+ }
1818
+ }
1810
1819
  const properties = propertiesOfStructuredType(type);
1811
1820
  Object.keys(properties).forEach(iName => {
1812
1821
  const property = properties[iName];
1813
1822
  if (suffix === SUFFIX.read) schemaProperties[iName] = getSchema(property);
1814
1823
  if ((Object.prototype.hasOwnProperty.call(property, '@Common.FieldControl')) && property['@Common.FieldControl'] === 'Mandatory') { required.push(iName) }
1815
1824
  if (property.$Kind == 'NavigationProperty') {
1816
- if (property.$Collection && suffix === "") {
1825
+ if (property.$Collection && suffix === "" && isCount === true) {
1817
1826
  schemaProperties[`${iName}@${csdl.$Version === '4.0' ? 'odata.' : ''}count`] = ref('count');
1818
1827
  }
1819
1828
  if (property[voc.Core.Permissions] != "Read" && !property[voc.Core.Computed] && property.$ContainsTarget) {
@@ -34,11 +34,15 @@ function _getOpenApi(csdl, options) {
34
34
  return openapi;
35
35
  }
36
36
 
37
- function toOpenApiOptions(csdl, csn, options={}) {
37
+ function toOpenApiOptions(csdl, csn, options = {}) {
38
38
  const result = {}
39
39
  for (const key in options) {
40
- if (/^openapi:(.*)/.test(key) && RegExp.$1)
41
- result[RegExp.$1] = options[key]
40
+ if (/^openapi:(.*)/.test(key) && RegExp.$1) {
41
+ result[RegExp.$1] = options[key];
42
+ }
43
+ else if (key === 'odata-version') {
44
+ result['odataVersion'] = options[key];
45
+ }
42
46
  }
43
47
  if (result.url) {
44
48
  result.url = result.url.replace(/\/*\$\{service-path\}/g, servicePath(csdl, csn))
@@ -7,7 +7,7 @@ const messages = require("./message").getMessages();
7
7
  const cds = require('../cds');
8
8
  const { fs, path, write, mkdirp, exists, read } = cds.utils;
9
9
  const cdsCompiler = require('..').compile.to;
10
- const md5 = require('md5');
10
+ const md5 = data => require('crypto').createHash('md5').update(data).digest('hex');
11
11
 
12
12
  let optionsMapping = {
13
13
  'keep-namespace': 'keepNamespace',
@@ -29,7 +29,12 @@ const messages = {
29
29
  UNSUPPORTED_PROPERTY_TYPE: 'Property cannot be of EntityType. Check the type: ',
30
30
  UNRESOLVED_SCHEMA_NAMESPACE: `Could not resolve the schema `,
31
31
  INVALID_DATATYPE: `Invalid Data Type `,
32
- ELEMENT_NAME_COLLISION: 'Collision between EntityContainer Element Name and Schema Element Name is currenlty not supported. Please check the element name: '
32
+ ELEMENT_NAME_COLLISION: 'Collision between EntityContainer Element Name and Schema Element Name is currenlty not supported. Please check the element name: ',
33
+ NO_DEFAULT_FOR_COLLECTIONS: 'WARNING: Collection type cannot have a Default value.',
34
+ BINARY_MAXLENGTH: 'WARNING: MaxLength for type Edm.Binary should not exceed 5000.',
35
+ SCALE_GT_PRECISION: 'WARNING: Scale value should not be greater than Precision value.',
36
+ FIXED_PRECISION_VARIABLE_SCALE: 'WARNING: Fixed Precision with Variable or Floating Scale is not supported in CDS.',
37
+ NULLABLE_KEY: 'WARNING: Key element cannot be Nullable.'
33
38
  };
34
39
 
35
40
  function getMessages() {
@@ -450,22 +450,26 @@ class Expression {
450
450
  XML: If no value is specified, the Scale facet defaults to zero.
451
451
  JSON: If no value is specified, the Scale facet defaults to zero
452
452
 
453
- XML | JSON
454
- -----------------------
455
- variable | variable
456
- 1-* | 1-*
457
- 0 | undefined
458
- undefined | undefined
453
+ OLD NEW
454
+ XML | JSON XML | JSON
455
+ --------------------- -----------------------
456
+ variable | variable variable | variable
457
+ 1-* | 1-* 0-* | 0-*
458
+ 0 | undefined undefined | undefined
459
+ undefined | undefined
460
+
461
+ * In case EDMX doesn't specify Scale, we are not adding default `0` for it anymore
462
+ * beause we want to easily identify and preserve the explicitly mentioned Scale="0" in EDMX
459
463
  */
460
464
  if (args.includes('$Scale') && (target.$Type === 'Edm.Decimal' || target.$UnderlyingType === 'Edm.Decimal')) {
461
465
  if (attribs.Scale != null) {
462
466
  if (attribs.Scale === 'variable' || attribs.Scale === 'floating') {
463
467
  target.$Scale = attribs.Scale;
464
- } else if (attribs.Scale !== '0') {
468
+ }
469
+ // for now considering only non-negative integers
470
+ else if (Number(attribs.Scale) >= 0) {
465
471
  target.$Scale = parseInt(attribs.Scale, 10);
466
472
  }
467
- } else {
468
- target.$Scale = 0;
469
473
  }
470
474
  }
471
475
 
@@ -190,10 +190,11 @@ function _updateElementObjectForDefaultValue(dataTypeObj, defaultvalue) {
190
190
  // for collection, default value isin't expected
191
191
  if (dataTypeObj.items === undefined) {
192
192
  dataTypeObj.default = {};
193
- dataTypeObj.default.val = (defaultvalue) ? defaultvalue : null;
193
+ // defaultvalue could be of type string (eg., '0') or number (eg., 0 or -1.5)
194
+ dataTypeObj.default.val = (defaultvalue !== undefined) ? defaultvalue : null;
194
195
  }
195
196
  else {
196
- console.log(warn("Collection type cannot have a Default value."));
197
+ console.log(warn(messages.NO_DEFAULT_FOR_COLLECTIONS));
197
198
  }
198
199
  }
199
200
 
@@ -204,22 +205,29 @@ function _updateDataTypeObjectForMaxLength(dataTypeObj, propertyDataType, maxLen
204
205
  dataTypeObj.type = mappedValue
205
206
  dataTypeObj.length = maxLength;
206
207
  }
207
- if (propertyDataType === 'Edm.Binary' && maxLength >= 5000) {
208
- console.log(warn("MaxLength for type Edm.Binary should not exceed 5000"));
208
+ if (propertyDataType === 'Edm.Binary' && maxLength > 5000) {
209
+ console.log(warn(messages.BINARY_MAXLENGTH));
209
210
  }
210
211
  }
211
212
 
212
213
  function _updateDataTypeObjectForPrecision(dataTypeObj, propertyDataType, precision, scale) {
213
214
  const mappedValue = edmToCdsTypeMappingWithPrecision[propertyDataType];
215
+ const numberedScale = Number(scale);
214
216
 
215
217
  if (mappedValue) {
216
218
  dataTypeObj.type = mappedValue;
217
- if (Number(precision) > 0) {
219
+ if (precision >= 0) {
218
220
  dataTypeObj.precision = precision;
219
221
  }
220
- if (Number(scale) > 0 || scale === 'floating' || scale === 'variable') {
222
+ if (numberedScale >= 0) {
221
223
  dataTypeObj.scale = scale;
222
224
  }
225
+ if (numberedScale > precision) {
226
+ console.log(warn(messages.SCALE_GT_PRECISION));
227
+ }
228
+ if (precision >= 0 && (scale === 'floating' || scale === 'variable')) {
229
+ console.log(warn(messages.FIXED_PRECISION_VARIABLE_SCALE));
230
+ }
223
231
  }
224
232
  }
225
233
 
@@ -469,7 +477,7 @@ function _generatePrimitiveDataTypeCsn(propertyObject, isPrimaryKey, parserConte
469
477
  if (isPrimaryKey) {
470
478
  dataTypeObj.key = true;
471
479
  if (nullable) {
472
- console.log(warn("Key element cannot be Nullable."));
480
+ console.log(warn(messages.NULLABLE_KEY));
473
481
  }
474
482
  }
475
483
  _captureAnnotationWithinElement(propertyObject, dataTypeObj, parserContext);
@@ -479,7 +487,11 @@ function _generatePrimitiveDataTypeCsn(propertyObject, isPrimaryKey, parserConte
479
487
  if (maxLength) {
480
488
  _updateDataTypeObjectForMaxLength(dataTypeObj, propertyDataType, maxLength);
481
489
  }
482
- if (precision || scale) {
490
+ /**
491
+ * Precision: non-negative integers
492
+ * Scale: non-negative integers, variable, floating
493
+ */
494
+ if (precision >= 0 || scale !== undefined) {
483
495
  _updateDataTypeObjectForPrecision(dataTypeObj, propertyDataType, precision, scale);
484
496
  }
485
497
  if (edmToCdsTypeSpecialHandling[propertyDataType]) {
@@ -503,7 +515,8 @@ function _generatePrimitiveDataTypeCsn(propertyObject, isPrimaryKey, parserConte
503
515
  if (isCollection) {
504
516
  _updateElementObjectForCollection(dataTypeObj, isPrimaryKey, propertyDataType);
505
517
  }
506
- if (defaultvalue) {
518
+ // defaultvalue could be of type string (eg., '0') or number (eg., 0 or -1.5)
519
+ if (defaultvalue !== undefined) {
507
520
  _updateElementObjectForDefaultValue(dataTypeObj, defaultvalue);
508
521
  }
509
522
 
@@ -538,7 +551,7 @@ function _generateStructuredDataTypeCsn(dataType, propertyObject, isPrimaryKey,
538
551
  if (isPrimaryKey) {
539
552
  dataTypeObj.key = true;
540
553
  if (nullable) {
541
- console.log(warn("Key element cannot be Nullable."));
554
+ console.log(warn(messages.NULLABLE_KEY));
542
555
  }
543
556
  }
544
557
 
@@ -566,9 +579,10 @@ function _generateStructuredDataTypeCsn(dataType, propertyObject, isPrimaryKey,
566
579
  * but compiler accepts it for TypeDefintion and ComplexType as well,
567
580
  * do we also need to handle it for TypeDefintion and ComplexType?
568
581
  *
569
- * For parameters, default value is added via OptionalParamter Annotation
582
+ * For parameters, defaultvalue is added via OptionalParamter Annotation
583
+ * defaultvalue could be of type string (eg., '0') or number (eg., 0 or -1.5)
570
584
  */
571
- if (defaultvalue && dataTypeKind === 'EnumType' && elementKind === 'property') {
585
+ if (defaultvalue !== undefined && dataTypeKind === 'EnumType' && elementKind === 'property') {
572
586
  _updateElementObjectForDefaultValue(dataTypeObj, defaultvalue);
573
587
  }
574
588
  else {
@@ -1,14 +1,10 @@
1
- const os = require('os');
2
-
3
- const MAVEN_ARCHETYPE_VERSION = '2.0.2';
4
-
5
- const constants = {
6
- IS_WIN: (os.platform() === 'win32'),
1
+ module.exports = {
2
+ IS_WIN: require('os').platform() === 'win32',
7
3
 
8
4
  COMMAND_INIT: 'init',
9
5
  COMMAND_ADD: 'add',
10
6
 
11
- MAVEN_ARCHETYPE_VERSION,
7
+ MAVEN_ARCHETYPE_VERSION: '2.2.0',
12
8
 
13
9
  OPTIONS: Object.freeze({
14
10
  JAVA: 'java',
@@ -29,7 +25,7 @@ const constants = {
29
25
  XSUAA: 'xsuaa',
30
26
  HANA: 'hana',
31
27
  SQLITE: 'sqlite',
32
- AUDITLOG: 'auditlog',
28
+ H2: 'h2',
33
29
  ENTERPRISE_MESSAGING: 'enterprise-messaging',
34
30
  ENTERPRISE_MESSAGING_SHARED: 'enterprise-messaging-shared',
35
31
  FILE_BASED_MESSAGING: 'file-based-messaging',
@@ -48,6 +44,8 @@ const constants = {
48
44
  PROJECT_TYPE: Object.freeze({ nodejs: 1, java: 2, unknown: 3 }),
49
45
 
50
46
  REGEX_JAVA_PACKAGE: /^[a-z_][a-z0-9_]*(\.[a-z_][a-z0-9_]*)*$/g,
47
+ // copied from mbt error message
48
+ REGEX_JAVA_VERSION: /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$/,
51
49
  REGEX_PROJECT_NAME: /[^0-9a-zA-Z_-]/g,
52
50
 
53
51
  URLS: Object.freeze({
@@ -57,11 +55,8 @@ const constants = {
57
55
  MAVEN_ARCHETYPE_HELP: `https://cap.cloud.sap/docs/java/development/#the-maven-archetype`,
58
56
  MAVEN_INSTALL_HELP: `https://maven.apache.org/install.html`,
59
57
  VSCODE: 'https://code.visualstudio.com/',
60
- VSCODE_NBVIEWER: 'https://marketplace.visualstudio.com/items?itemName=ms-python.python',
61
58
  VSCODE_ESLINT: 'https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint'
62
59
  }),
63
60
 
64
61
  PROJECT_FILES: ['package.json', 'pom.xml', '.cdsrc.json']
65
62
  }
66
-
67
- module.exports = constants;