@sap/cds-dk 7.1.1 → 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 (50) hide show
  1. package/bin/build.js +3 -3
  2. package/bin/compile/index.js +5 -1
  3. package/bin/gen.js +27 -0
  4. package/bin/help.js +2 -1
  5. package/bin/watch.js +3 -4
  6. package/bin/watched.js +2 -3
  7. package/lib/bind/bindingUtil.js +1 -0
  8. package/lib/build/buildTaskEngine.js +12 -17
  9. package/lib/build/buildTaskFactory.js +23 -27
  10. package/lib/build/buildTaskHandler.js +2 -5
  11. package/lib/build/buildTaskProviderFactory.js +9 -12
  12. package/lib/build/constants.js +2 -1
  13. package/lib/build/index.js +1 -1
  14. package/lib/build/provider/buildTaskHandlerInternal.js +2 -2
  15. package/lib/build/provider/buildTaskProviderInternal.js +15 -25
  16. package/lib/build/provider/hana/index.js +14 -7
  17. package/lib/build/provider/nodejs/index.js +51 -18
  18. package/lib/build/util.js +11 -19
  19. package/lib/compile/openapi/csdl2openapi.js +30 -21
  20. package/lib/compile/openapi/index.js +7 -3
  21. package/lib/import/common.js +1 -1
  22. package/lib/init/constants.js +6 -11
  23. package/lib/init/index.js +84 -71
  24. package/lib/init/template/_merging/registry-mta.js +0 -12
  25. package/lib/init/template/approuter/files/mta.yaml.hbs +1 -1
  26. package/lib/init/template/approuter/index.js +7 -6
  27. package/lib/init/template/cf-manifest/files/services-manifest.yml.hbs +0 -5
  28. package/lib/init/template/cf-manifest/index.js +2 -2
  29. package/lib/init/template/h2/index.js +16 -0
  30. package/lib/init/template/hana/index.js +2 -3
  31. package/lib/init/template/helm/chart/values.root.schema.json +0 -5
  32. package/lib/init/template/helm/chart/values.schema.json +0 -4
  33. package/lib/init/template/helm/index.js +1 -1
  34. package/lib/init/template/lint/index.js +3 -7
  35. package/lib/init/template/multitenancy/files/package.json.hbs +0 -3
  36. package/lib/init/template/multitenancy/index.js +2 -0
  37. package/lib/init/template/nodejs/index.js +3 -3
  38. package/lib/init/template/pipeline/index.js +2 -4
  39. package/lib/init/template/sample/index.js +5 -7
  40. package/lib/init/template/sqlite/index.js +8 -3
  41. package/lib/init/template/tiny-sample/index.js +16 -30
  42. package/lib/init/util/mvn.js +25 -1
  43. package/lib/init/util/projectReader.js +3 -7
  44. package/lib/init/util/template.js +7 -6
  45. package/lib/watch/node-watch.js +6 -1
  46. package/npm-shrinkwrap.json +324 -307
  47. package/package.json +3 -3
  48. package/lib/init/template/auditlog/files/mta.yaml.hbs +0 -14
  49. package/lib/init/template/auditlog/files/package.json.hbs +0 -27
  50. package/lib/init/template/auditlog/index.js +0 -74
@@ -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',
@@ -1,14 +1,10 @@
1
- const os = require('os');
2
-
3
- const MAVEN_ARCHETYPE_VERSION = '2.1.0';
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;
package/lib/init/index.js CHANGED
@@ -3,7 +3,12 @@ const path = require('path');
3
3
  const term = require('../util/term');
4
4
 
5
5
  const { OPTIONS, COMMAND_INIT, PROJECT_FILES, REGEX_PROJECT_NAME } = require('./constants')
6
- const { NODEJS, JAVA, HANA, MTA,PIPELINE, TINY_SAMPLE, CF_MANIFEST } = OPTIONS
6
+ const {
7
+ NODEJS, JAVA,
8
+ HANA, APPROUTER, XSUAA, KIBANA, ENTERPRISE_MESSAGING,
9
+ MTA, HELM, MULTITENANCY, PIPELINE,
10
+ TINY_SAMPLE, SAMPLE
11
+ } = OPTIONS
7
12
 
8
13
  const cds = require('../cds'), { exists, readdir } = cds.utils
9
14
  const DEBUG = /\b(y|all|cli)\b/.test(process.env.DEBUG) ? console.debug : undefined
@@ -12,77 +17,86 @@ module.exports = class CDSGenerator {
12
17
  constructor() {
13
18
  this.cwd = process.cwd();
14
19
 
15
- this.uiConfig = [
16
- {
17
- title: `Project Name`,
18
- description: `Enter your project name.`,
19
- default: '',
20
- target: 'projectName',
21
- validate: async (value) => {
22
- try {
23
- cds.root = path.resolve(this.cwd, value || '.');
24
-
25
- await this._validateProjectName(value);
26
- await this._validateProjectFolder(path.join(this.cwd, value), this.cwd);
27
-
28
- return true;
29
- } catch (err) {
30
- return err;
31
- }
32
- },
33
- guiOptions: {
34
- hint: 'This name is restricted to certain characters. A valid project name is compatible across platforms.',
35
- mandatory: true
36
- }
37
- },
38
- {
39
- title: `Type`,
40
- description: `Select your runtime.`,
41
- default: NODEJS,
42
- choices: [
43
- { name: 'Node.js', value: NODEJS },
44
- { name: 'Java', value: JAVA }
45
- ],
46
- multiple: false,
47
- target: 'add',
48
- guiOptions: {
49
- hint: `The preferred runtime can't be changed afterwards. To use a different runtime, create a new project.`,
50
- mandatory: true
51
- }
52
- },
53
- {
54
- title: `Features`,
55
- description: `Add features to your project.`,
56
- default: '',
57
- choices: [
58
- { name: 'CI/CD Pipeline Integration', value: PIPELINE },
59
- { name: 'Configuration for SAP HANA Deployment', value: HANA },
60
- { name: 'MTA based SAP Business Technology Platform Deployment', value: MTA },
61
- // { name: 'Multitenancy', value: mtx },
62
- { name: `Cloud Foundry native Deployment`, value: CF_MANIFEST }
63
- // { name: `CDS Linter for custom rules`, value: lint },
64
- // { name: `CDS Linter for custom rules with sample`, value: lintDev }
65
- ],
66
- multiple: true,
67
- target: 'add',
68
- guiOptions: {
69
- hint: `Choose any combination of features enriching the capabilities of your project. You can also add features later with ${term.bold('cds add')}`
20
+ this.uiConfig = [{
21
+ title: 'Project Name',
22
+ description: 'Enter your project name.',
23
+ default: '',
24
+ target: 'projectName',
25
+ validate: async (value) => {
26
+ try {
27
+ cds.root = path.resolve(this.cwd, value || '.');
28
+
29
+ await this._validateProjectName(value);
30
+ await this._validateProjectFolder(path.join(this.cwd, value), this.cwd);
31
+ } catch (e) {
32
+ return e
70
33
  }
34
+
35
+ return true;
71
36
  },
72
- {
73
- title: `Samples`,
74
- description: `Add sample content to your project.`,
75
- default: '',
76
- choices: [
77
- { name: 'Basic Sample Files', value: TINY_SAMPLE }
78
- ],
79
- multiple: true,
80
- target: 'add',
81
- guiOptions: {
82
- hint: 'Speed up your ramp-up phase with some sample files in your project.'
83
- }
37
+ guiOptions: {
38
+ hint: 'This name is restricted to certain characters. A valid project name is compatible across platforms.',
39
+ mandatory: true
40
+ }
41
+ }, {
42
+ title: 'Type',
43
+ description: `Select your runtime.`,
44
+ default: NODEJS,
45
+ choices: [
46
+ { name: 'Node.js', value: NODEJS },
47
+ { name: 'Java', value: JAVA }
48
+ ],
49
+ multiple: false,
50
+ target: 'add',
51
+ guiOptions: {
52
+ hint: `The preferred runtime can't be changed afterwards. To use a different runtime, create a new project.`,
53
+ mandatory: true
54
+ }
55
+ }, {
56
+ title: 'Features',
57
+ description: 'Choose productive runtime capabilities for your application.',
58
+ default: '',
59
+ choices: [
60
+ { name: 'SAP HANA Cloud', value: HANA },
61
+ { name: 'SAP Application Router', value: APPROUTER },
62
+ { name: 'User Authentication via XSUAA', value: XSUAA },
63
+ { name: 'Kibana Logging Dashboard', value: KIBANA },
64
+ { name: 'SAP BTP Event Mesh', value: ENTERPRISE_MESSAGING }
65
+ ],
66
+ multiple: true,
67
+ target: 'add',
68
+ guiOptions: {
69
+ hint: `Choose any combination of features enriching the capabilities of your project. You can also add features later with ${term.bold('cds add')}`
70
+ }
71
+ }, {
72
+ title: 'Deployment',
73
+ description: 'Choose which way to deploy your project.',
74
+ default: '',
75
+ choices: [
76
+ { name: 'Cloud Foundry: MTA Deployment', value: MTA },
77
+ { name: 'Kyma: Helm Deployment', value: HELM },
78
+ { name: 'Multitenancy', value: MULTITENANCY },
79
+ { name: 'CI/CD Pipeline Integration', value: PIPELINE },
80
+ ],
81
+ multiple: true,
82
+ target: 'add',
83
+ guiOptions: {
84
+ hint: `Choose any combination of features enriching the capabilities of your project. You can also add features later with ${term.bold('cds add')}`
85
+ }
86
+ }, {
87
+ title: 'Samples',
88
+ description: `Add sample content to your project.`,
89
+ default: '',
90
+ choices: [
91
+ { name: 'Minimal Sample', value: TINY_SAMPLE },
92
+ { name: 'Extended Sample with UI', value: SAMPLE }
93
+ ],
94
+ multiple: true,
95
+ target: 'add',
96
+ guiOptions: {
97
+ hint: 'Speed up your ramp-up phase with sample files in your project.'
84
98
  }
85
- ]
99
+ }]
86
100
  }
87
101
 
88
102
  /**
@@ -190,8 +204,7 @@ module.exports = class CDSGenerator {
190
204
  );
191
205
  const fuzzySearch = require('../../bin/util/fuzzySearch');
192
206
  const [bestMatch] = fuzzySearch(filename, entries);
193
- console.log(`Unknown facet '${term.bold(filename)}'. Did you mean ${term.bold(`cds add ${bestMatch}`)}?\n\nHaven't found the proper facet yet? Here are all supported facets:\n\n ${term.bold(entries.join('\n '))}\n`);
194
- process.exit(1);
207
+ throw `Unknown facet '${term.bold(filename)}'. Did you mean ${term.bold(`cds add ${bestMatch}`)}?\n\nHaven't found the proper facet yet? Here are all supported facets:\n\n ${term.bold(entries.join('\n '))}\n`
195
208
  }
196
209
  throw err;
197
210
  }
@@ -60,17 +60,6 @@ const connectivity = {
60
60
  }]
61
61
  }
62
62
 
63
- const auditlog = {
64
- in: 'resources',
65
- where: [{
66
- property: 'type',
67
- isEqualTo: 'org.cloudfoundry.managed-service',
68
- }, {
69
- property: 'parameters.service',
70
- isEqualTo: 'auditlog-api',
71
- }]
72
- }
73
-
74
63
  const redis = {
75
64
  in: 'resources',
76
65
  where: [{
@@ -260,7 +249,6 @@ module.exports = {
260
249
  srvJava4,
261
250
  approuter,
262
251
  xsuaa,
263
- auditlog,
264
252
  destination,
265
253
  connectivity,
266
254
  enterpriseMessaging,
@@ -19,7 +19,7 @@ modules:
19
19
 
20
20
  - name: {{appName}}
21
21
  type: approuter.nodejs
22
- path: {{& appPath}} # from cds.env.folders. Consider also cds.env.build.target -> gen/app
22
+ path: {{& approuterPath }}
23
23
  parameters:
24
24
  keep-existing-routes: true
25
25
  disk-quota: 256M