@sap/cds-dk 8.4.1 → 8.5.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 (52) hide show
  1. package/CHANGELOG.md +43 -2
  2. package/bin/add.js +2 -12
  3. package/bin/cds.js +5 -0
  4. package/bin/debug.js +90 -0
  5. package/bin/watch.js +12 -2
  6. package/lib/bind/index.js +1 -1
  7. package/lib/bind/userCdsrcJsonStorageProvider.js +0 -19
  8. package/lib/build/buildTaskEngine.js +1 -1
  9. package/lib/build/provider/hana/2migration.js +34 -20
  10. package/lib/build/provider/hana/index.js +74 -41
  11. package/lib/build/provider/internalBuildTaskProvider.js +1 -1
  12. package/lib/build/provider/mtx-extension/index.js +4 -4
  13. package/lib/build/provider/mtx-sidecar/index.js +2 -2
  14. package/lib/build/provider/nodejs/index.js +3 -19
  15. package/lib/build/util.js +41 -17
  16. package/lib/deploy/to-hana/hdiDeployUtil.js +14 -10
  17. package/lib/import/message.js +2 -1
  18. package/lib/import/odata/metadataconverter/MetadataConverter.js +5 -0
  19. package/lib/import/odata/v4parser.js +32 -43
  20. package/lib/index.js +14 -14
  21. package/lib/init/add.js +26 -0
  22. package/lib/init/constants.js +5 -6
  23. package/lib/init/index.js +17 -25
  24. package/lib/init/merge.js +1 -4
  25. package/lib/init/projectReader.js +75 -9
  26. package/lib/init/registries/mta.js +0 -1
  27. package/lib/init/template/approuter/files/package.json +1 -1
  28. package/lib/init/template/cloud-logging/files/package.json.hbs +1 -12
  29. package/lib/init/template/cloud-logging/files/values.yaml.hbs +16 -0
  30. package/lib/init/template/cloud-logging/index.js +14 -10
  31. package/lib/init/template/data/as-csv.js +1 -1
  32. package/lib/init/template/data/as-json.js +5 -5
  33. package/lib/init/template/data/index.js +1 -26
  34. package/lib/init/template/esm/files/package.json.hbs +3 -0
  35. package/lib/init/template/esm/index.js +16 -0
  36. package/lib/init/template/hana/files/package.json.hbs +18 -4
  37. package/lib/init/template/handler/files/handler.xs.hbs +56 -0
  38. package/lib/init/template/handler/index.js +84 -9
  39. package/lib/init/template/helm/index.js +4 -1
  40. package/lib/init/template/http/index.js +108 -39
  41. package/lib/init/template/sample/files/nodejs/{admin-service.js → admin-service.js.hbs} +8 -1
  42. package/lib/init/template/sample/files/nodejs/{cat-service.js → cat-service.js.hbs} +8 -3
  43. package/lib/init/template/sample/index.js +4 -2
  44. package/lib/init/template/side-by-side-extensibility/index.js +1 -0
  45. package/lib/init/template/telemetry/files/package.json.hbs +18 -0
  46. package/lib/init/template/telemetry/index.js +8 -2
  47. package/lib/init/template/typescript/files/package.json +1 -1
  48. package/lib/init/template/workzone-standard/index.js +1 -1
  49. package/lib/init/types.d.ts +0 -6
  50. package/lib/watch/node-watch.js +25 -9
  51. package/npm-shrinkwrap.json +28 -33
  52. package/package.json +1 -1
@@ -3,7 +3,7 @@ const path = require('path')
3
3
  const cds = require('../../../cds')
4
4
  const cmd = require('../../../util/command');
5
5
  const EdmxBuildPlugin = require('../edmxBuildPlugin')
6
- const { BuildError, getWorkspaces } = require('../../util')
6
+ const { BuildError, getWorkspaces, findWorkspaceRoot } = require('../../util')
7
7
  const { OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM, ODATA_VERSION_V2, FOLDER_GEN, CONTENT_EDMX, CONTENT_PACKAGELOCK_JSON,
8
8
  CONTENT_NPMRC, CONTENT_CDSRC_JSON, CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, FLAVOR_LOCALIZED_EDMX,
9
9
  OPTION_WS_PACK} = require('../../constants')
@@ -160,14 +160,14 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
160
160
  }
161
161
 
162
162
  async _packWorkspaceDependencies(dest) {
163
- const wsRoot = NodejsBuildPlugin._findWorkspaceRoot(cds.root)
163
+ const wsRoot = findWorkspaceRoot(cds.root)
164
164
  const appPkg = require(path.join(dest, 'package.json'))
165
165
  let workspaces
166
166
  if (this.task.options.workspaces) {
167
167
  workspaces = Array.isArray(this.task.options.workspaces) ? this.task.options.workspaces : [this.task.options.workspaces]
168
168
  } else {
169
169
  // devDependencies are not considered
170
- workspaces = await getWorkspaces(true, false, wsRoot);
170
+ workspaces = await getWorkspaces(wsRoot, true, false);
171
171
  }
172
172
  if (workspaces.length) {
173
173
  let pkgDescriptors
@@ -197,22 +197,6 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
197
197
  }
198
198
  }
199
199
 
200
- static _findWorkspaceRoot(dir) {
201
- try {
202
- if (require(path.join(dir, 'package.json')).workspaces) {
203
- return dir
204
- }
205
- } catch (err) {
206
- // ignore
207
- }
208
- if (fs.existsSync(path.join(dir, '.gitmodules'))) {
209
- return // project root reached
210
- }
211
- if (path.dirname(dir) !== dir) {
212
- return this._findWorkspaceRoot(path.dirname(dir))
213
- }
214
- }
215
-
216
200
  static async _execNpmPack(dest, wsRoot, workspaces) {
217
201
  const args = ['pack', '-ws', '--json', `--pack-destination=${dest}`]
218
202
  workspaces.forEach(w => args.push(`-w=${w}`))
package/lib/build/util.js CHANGED
@@ -7,6 +7,7 @@ const IS_WIN = require('os').platform() === 'win32'
7
7
  const { SEVERITY_ERROR, FILE_EXT_CDS } = require('./constants');
8
8
  const DEBUG = cds.debug('cli|build')
9
9
  const SECRETS = /(passw)|(cert)|(ca)|(secret)|(key)/i
10
+ const WS_MAX_DEPTH = 2
10
11
 
11
12
  function getProperty(src, segments) {
12
13
  segments = Array.isArray(segments) ? segments : segments.split('.')
@@ -98,13 +99,13 @@ function _redacted(cred) {
98
99
  if (!cred) return cred
99
100
  if (Array.isArray(cred)) return cred.map(c => typeof c === 'string' ? '...' : _redacted(c))
100
101
  if (typeof cred === 'object') {
101
- const newCred = Object.assign({}, cred)
102
- Object.keys(newCred).forEach(k => (typeof newCred[k] === 'string' && SECRETS.test(k)) ? (newCred[k] = '...') : (newCred[k] = _redacted(newCred[k])))
103
- return newCred
102
+ const newCred = Object.assign({}, cred)
103
+ Object.keys(newCred).forEach(k => (typeof newCred[k] === 'string' && SECRETS.test(k)) ? (newCred[k] = '...') : (newCred[k] = _redacted(newCred[k])))
104
+ return newCred
104
105
  }
105
106
  return cred
106
107
  }
107
-
108
+
108
109
  /**
109
110
  * Returns a list of fully qualified model names belonging to the '@sap' namespace that cannot be resolved.
110
111
  * E.g. the module might NOT have been installed.
@@ -173,28 +174,30 @@ async function pathExists(path) {
173
174
  return fs.promises.access(path).then(() => true).catch(() => false);
174
175
  }
175
176
 
177
+ async function getWorkspacePaths() {
178
+ const wsRoot = findWorkspaceRoot(cds.root)
179
+ if (!wsRoot) {
180
+ return []
181
+ }
182
+ return (await getWorkspaces(wsRoot)).filter(ws => cds.root !== path.join(wsRoot, ws)).map(ws => path.join(wsRoot, ws))
183
+ }
184
+
176
185
  /**
177
186
  * Returns the list of workspaces defined for the current project. A workspace dependency
178
187
  * needs to be defined resulting in a corresponding sym-link.
179
- * @returns the paths are relative to 'cds.root'.
188
+ * @returns the paths are relative to 'wsRoot'.
180
189
  */
181
- async function getWorkspaces(resolve = false, devDependencies = true, wsRoot = cds.root) {
182
- let pgkJsonWsRoot
183
- try {
184
- pgkJsonWsRoot = require(path.join(wsRoot, 'package.json'))
185
- } catch (e) {
186
- if (e.code === 'MODULE_NOT_FOUND') {
187
- DEBUG?.(`No package.json found in project '${wsRoot}', skipping workspace resolution.`)
188
- return []
189
- }
190
- throw e
190
+ async function getWorkspaces(wsRoot, resolve = false, devDependencies = true) {
191
+ wsRoot ??= findWorkspaceRoot(cds.root)
192
+ if (!wsRoot) {
193
+ return []
191
194
  }
192
-
193
- const workspaces = []
195
+ const pgkJsonWsRoot = require(path.join(wsRoot, 'package.json'))
194
196
  let pgkJson = pgkJsonWsRoot
195
197
  if (wsRoot !== cds.root) {
196
198
  pgkJson = require(path.join(cds.root, 'package.json'))
197
199
  }
200
+ const workspaces = []
198
201
  if (pgkJsonWsRoot.workspaces) {
199
202
  if (resolve) {
200
203
  _validateWsDependencies(wsRoot, pgkJson.dependencies, devDependencies ? pgkJson.devDependencies : {})
@@ -215,6 +218,25 @@ async function getWorkspaces(resolve = false, devDependencies = true, wsRoot = c
215
218
  return workspaces
216
219
  }
217
220
 
221
+ function findWorkspaceRoot(currentPath, depth = 0) {
222
+ if (depth > WS_MAX_DEPTH) {
223
+ DEBUG?.(`No workspaces exist, max depth of ${WS_MAX_DEPTH} reached`)
224
+ return
225
+ }
226
+ const packageJson = path.join(currentPath, 'package.json')
227
+ if (fs.existsSync(packageJson)) {
228
+ if (require(path.join(currentPath, 'package.json')).workspaces) {
229
+ return currentPath
230
+ }
231
+ }
232
+ if (fs.existsSync(path.join(currentPath, '.gitmodules'))) {
233
+ return // project root reached
234
+ }
235
+ if (path.dirname(currentPath) !== currentPath) {
236
+ return findWorkspaceRoot(path.dirname(currentPath), depth + 1)
237
+ }
238
+ }
239
+
218
240
  /**
219
241
  * This JavaScript function filterFeaturePaths is used to filter and categorize file paths
220
242
  * from a given model into two categories: base and features.
@@ -452,7 +474,9 @@ module.exports = {
452
474
  getI18nDefaultFolder,
453
475
  normalizePath,
454
476
  pathExists,
477
+ getWorkspacePaths,
455
478
  getWorkspaces,
479
+ findWorkspaceRoot,
456
480
  filterFeaturePaths,
457
481
  getFiles,
458
482
  BuildMessage,
@@ -1,5 +1,4 @@
1
1
  const path = require('path');
2
- const util = require('util');
3
2
  const { bold } = require('../../util/term');
4
3
 
5
4
  const HDI_DEPLOY_LIB = '@sap/hdi-deploy';
@@ -40,15 +39,20 @@ module.exports = new class HdiDeployUtil {
40
39
  if (options.use_hdb === false) options.use_hdb = undefined
41
40
  deployerEnv.HDI_DEPLOY_OPTIONS = JSON.stringify(options)
42
41
 
43
- const asyncDeploy = util.promisify(deploy);
44
- const { exitCode, signal } = await asyncDeploy(dbDir, deployerEnv);
45
- if (exitCode) {
46
- let message = `HDI deployment failed with exit code ${exitCode}`;
47
- if (signal) {
48
- message += `. ${signal}`;
49
- }
50
- throw new Error(message);
51
- }
42
+ return await new Promise((resolve, reject) => {
43
+ deploy(dbDir, deployerEnv, (error, response) => {
44
+ if (error) return reject(error)
45
+ if (response?.exitCode) {
46
+ let message = `HDI deployment failed with exit code ${response.exitCode}.`
47
+ if (response.signal) message += `. ${response.signal}`
48
+ return reject(new Error(message))
49
+ }
50
+ return resolve()
51
+ }, {
52
+ stderrCB: buffer => console.error(buffer.toString()),
53
+ stdoutCB: buffer => console.log(buffer.toString())
54
+ })
55
+ })
52
56
  }
53
57
 
54
58
  _loadHdiDeployLib(cwd) {
@@ -28,7 +28,7 @@ const messages = {
28
28
  BOUND_ACTION_COLLISION: 'The combination of Action name and binding parameter type must be unique for Bound Action ',
29
29
  STREAM_NO_COLLECTION: `'Edm.Stream' cannot be of Collection type.`,
30
30
  UNSUPPORTED_PROPERTY_TYPE: 'Property cannot be of EntityType. Check the type: ',
31
- UNRESOLVED_SCHEMA_NAMESPACE: `Could not resolve the schema `,
31
+ UNRESOLVED_SCHEMA_NAMESPACE: `Could not resolve the schema `,
32
32
  INVALID_DATATYPE: `Invalid Data Type `,
33
33
  ELEMENT_NAME_COLLISION: 'Collision between EntityContainer Element Name and Schema Element Name is currently not supported. Please check the element name: ',
34
34
  NO_DEFAULT_FOR_COLLECTIONS: 'WARNING: Collection type cannot have a Default value.',
@@ -38,6 +38,7 @@ const messages = {
38
38
  NULLABLE_KEY: 'WARNING: Key element cannot be Nullable.',
39
39
  COLLISION_DETECTED: `INFO: Element name collision detected. Adding suffix to affected type category.`,
40
40
  SPECIFY_CONFIG: 'For --config option, please specify correct JSON Object.',
41
+ INVALID_ANNOTATION: 'Annotation element must have a Term attribute',
41
42
  };
42
43
 
43
44
  function getMessages() {
@@ -1,4 +1,6 @@
1
1
  'use strict';
2
+
3
+ const message = require("../../message").getMessages();
2
4
 
3
5
  const vocabulariesMap = new Map([
4
6
  ['org.odata.core.v1', () => require('../vocabularies/Org.OData.Core.V1.json')],
@@ -1754,6 +1756,9 @@ class AnnotationExpression extends Expression {
1754
1756
  annotate(result) { super.annotate(this.getTargetPropertyName(), this.getTarget(), result); }
1755
1757
  interpret() {
1756
1758
  const element = this.getElement();
1759
+ if(!Object.hasOwn(element, "attributes") || !Object.hasOwn(element.attributes, "Term")) {
1760
+ throw new Error(message.INVALID_ANNOTATION);
1761
+ }
1757
1762
  let name = element.attributes.Term;
1758
1763
  if (element.attributes.Qualifier) name += '#' + element.attributes.Qualifier;
1759
1764
  name = '@' + name;
@@ -4,10 +4,10 @@
4
4
  */
5
5
 
6
6
  "use strict";
7
- let messages = require("../message").getMessages();
8
- let common = require("../common");
9
- let versionInfo = require("../../../package.json").version;
10
- let Multimap = require("./util/MultiMap");
7
+ const messages = require("../message").getMessages();
8
+ const common = require("../common");
9
+ const versionInfo = require("../../../package.json").version;
10
+ const Multimap = require("./util/MultiMap");
11
11
  const ignoreObjects = ["$Reference"]; // add all the objects that needs to be ignored for parsing.
12
12
  const { warn, info } = require("../../util/term");
13
13
  const MetadataConverterFactory = require("./metadataconverter/MetadataConverterFactory");
@@ -428,6 +428,11 @@ function _parseAnnotationTerms(annotationTerm, annotationTermObject, csnDefsTarg
428
428
  function _validateAnnotationNamespace(annotationTerm, vocabNamespaceToAlias) {
429
429
  let aliasOrNamespace = annotationTerm.substring(1, annotationTerm.lastIndexOf('.'));
430
430
 
431
+ // This check is for ignoring the @cds annotations given for special cases, e.g. @cds.validate = false for collided action/function
432
+ if (aliasOrNamespace === "cds") {
433
+ return
434
+ }
435
+
431
436
  for (let alias of Object.values(known_vocabularies)) {
432
437
  if (alias === aliasOrNamespace) {
433
438
  return;
@@ -1358,29 +1363,6 @@ function _parseEntityContainer(csnDefs, entityContainerElements, parserContext)
1358
1363
  return csnDefs;
1359
1364
  }
1360
1365
 
1361
- /**
1362
- * @param {List<Object>} dataObjList, action/function object list
1363
- * @returns unique parameter list
1364
- */
1365
- function _collectAllParameters(dataObjList) {
1366
- let uniqueParams = [], paramNames = [], params;
1367
-
1368
- for (let dataObj of dataObjList) {
1369
- params = dataObj.$Parameter;
1370
-
1371
- if (params) {
1372
- for (let i = 0; i < params.length; i++) {
1373
- if (!paramNames.includes(params[i].$Name)) {
1374
- paramNames.push(params[i].$Name);
1375
- uniqueParams.push(params[i]);
1376
- }
1377
- }
1378
- }
1379
- }
1380
-
1381
- return uniqueParams;
1382
- }
1383
-
1384
1366
  /**
1385
1367
  * @param {Boolean} isAction, to identify action/function
1386
1368
  * @param {String} fqName, fully qualified name
@@ -1389,7 +1371,7 @@ function _collectAllParameters(dataObjList) {
1389
1371
  *
1390
1372
  * Scenarios:
1391
1373
  * 1. Unbound Actions can't have collision. Action name must be unique.
1392
- * 2. Unbound Functions can have collision; capture the max number of parameters.
1374
+ * 2. Unbound Functions can have collision; we will parse only one function with the same name having no paramter and a generic String return type.
1393
1375
  */
1394
1376
  function _populateUnboundActionFunction(isAction, fqName, schemaDataList, parserContext) {
1395
1377
  let [schemaName, actFunName] = _extractSchemaNameAndElementName(fqName);
@@ -1409,8 +1391,12 @@ function _populateUnboundActionFunction(isAction, fqName, schemaDataList, parser
1409
1391
  }
1410
1392
  else {
1411
1393
  if (dataList.length > 1) {
1412
- actionFunctionObject['$Parameter'] = _collectAllParameters(dataList);
1394
+ actionFunctionObject['$Parameter'] = [];
1413
1395
  parserContext.unboundFunctions[fqName] = actionFunctionObject;
1396
+ parserContext.unboundFunctions[fqName]["@cds.validate"] = false;
1397
+ if(parserContext.unboundFunctions[fqName].$ReturnType){
1398
+ parserContext.unboundFunctions[fqName].$ReturnType.$Type = "Edm.String";
1399
+ }
1414
1400
  }
1415
1401
  else {
1416
1402
  parserContext.unboundFunctions[fqName] = actionFunctionObject;
@@ -1439,12 +1425,11 @@ function _collectUnboundActionFunction(schemaDataList, parserContext) {
1439
1425
  * @param {Object} parserContext
1440
1426
  *
1441
1427
  * Scenarios for bound actions, functions:
1442
- * 1. Actions with same name and binding parameter type can't have collision.
1443
- * 2. Functions can have collision and we will capture the max number of parameters.
1428
+ * 1. Actions with same name and binding parameter type can have collision and we will parse only one action with that name having no paramter and a genric String return type.
1429
+ * 2. Functions can have collision and we will parse only one function with that name having no paramter and a genric String return type.
1444
1430
  * Special scenario is when same function name is bound to different entity as well.
1445
1431
  */
1446
1432
  function _collectBoundActionFunction(isAction, currNamespace, elementName, elementDataList, parserContext) {
1447
- let functionObjList; // list to collect all the collision obj for function
1448
1433
 
1449
1434
  for (let i = 0; i < elementDataList.length; i++) {
1450
1435
  let dataObj = elementDataList[i];
@@ -1480,7 +1465,12 @@ function _collectBoundActionFunction(isAction, currNamespace, elementName, eleme
1480
1465
  parserContext.entityToBoundActions[boundEntityName][elementName] = dataObj;
1481
1466
  }
1482
1467
  else {
1483
- throw new Error(messages.BOUND_ACTION_COLLISION + `${elementName} in entity ${entityName} of schema ${currNamespace}.`);
1468
+ parserContext.entityToBoundActions[boundEntityName][elementName]["@cds.validate"] = false;
1469
+ parserContext.entityToBoundActions[boundEntityName][elementName]["$Parameter"] = [];
1470
+ if (!parserContext.entityToBoundActions[boundEntityName][elementName].$ReturnType) {
1471
+ parserContext.entityToBoundActions[boundEntityName][elementName].$ReturnType = {};
1472
+ }
1473
+ parserContext.entityToBoundActions[boundEntityName][elementName].$ReturnType.$Type = "Edm.String";
1484
1474
  }
1485
1475
  }
1486
1476
  else {
@@ -1488,18 +1478,12 @@ function _collectBoundActionFunction(isAction, currNamespace, elementName, eleme
1488
1478
  if (!parserContext.entityToBoundFunctions[boundEntityName]) {
1489
1479
  parserContext.entityToBoundFunctions[boundEntityName] = {};
1490
1480
  }
1491
- if (!functionObjList || !parserContext.entityToBoundFunctions[boundEntityName][elementName]) {
1492
- functionObjList = [];
1493
- }
1494
1481
  parserContext.entityToBoundFunctions[boundEntityName][elementName] = dataObj;
1495
- functionObjList.push(dataObj);
1496
1482
  }
1497
1483
  else {
1498
- functionObjList.push(dataObj);
1499
- let functionObject = functionObjList[0];
1500
- functionObject['$Parameter'] = _collectAllParameters(functionObjList);
1501
- functionObjList = [functionObject]; // reset the function object list with recently formed object
1502
- parserContext.entityToBoundFunctions[boundEntityName][elementName] = functionObject;
1484
+ parserContext.entityToBoundFunctions[boundEntityName][elementName]["@cds.validate"] = false;
1485
+ parserContext.entityToBoundFunctions[boundEntityName][elementName]["$Parameter"] = [];
1486
+ parserContext.entityToBoundFunctions[boundEntityName][elementName].$ReturnType.$Type = "Edm.String";
1503
1487
  }
1504
1488
  // throw error if the return type is missing
1505
1489
  if (!parserContext.entityToBoundFunctions[boundEntityName][elementName].$ReturnType) {
@@ -1688,7 +1672,12 @@ function _generateEDMX2JSON(edmx, name_space) {
1688
1672
  if (missingReferencedDocuments?.length > 0) {
1689
1673
  console.log(missingReferencedDocuments);
1690
1674
  }
1691
- resolve(json);
1675
+
1676
+ if (error) {
1677
+ reject(error);
1678
+ } else {
1679
+ resolve(json);
1680
+ }
1692
1681
  });
1693
1682
  } catch (err) {
1694
1683
  reject(err);
package/lib/index.js CHANGED
@@ -35,6 +35,20 @@ class CdsDk {
35
35
  }
36
36
  }
37
37
 
38
+ const REQUIRED_CDS_MAJOR = 7, REQUIRED_CDS_MINOR = 2
39
+ const [major, minor] = cds.version.split('.').map(Number)
40
+ if (major < REQUIRED_CDS_MAJOR || major === REQUIRED_CDS_MAJOR && minor < REQUIRED_CDS_MINOR) {
41
+ const [dkVersion] = require('../package.json').version.split('.')
42
+
43
+ console.error(`
44
+ This application uses '@sap/cds' version ${major}.${minor}, which is not compatible with the installed '@sap/cds-dk' version ${dkVersion}.
45
+ You can either:
46
+ - update '@sap/cds' to the latest version (recommended)
47
+ - downgrade '@sap/cds-dk' to version ${REQUIRED_CDS_MAJOR}
48
+ `)
49
+ process.exit(1)
50
+ }
51
+
38
52
  cds.extend (cds.constructor) .with (CdsDk)
39
53
 
40
54
  class CdsDkAdd {
@@ -49,20 +63,6 @@ if (process.argv[2] in { add:1, init:1, help:1 }) {
49
63
  cds.extend (cds.constructor) .with (CdsDkBuild)
50
64
  }
51
65
 
52
- const REQUIRED_CDS_MAJOR = 7, REQUIRED_CDS_MINOR = 2
53
- const [major, minor] = cds.version.split('.').map(Number)
54
- if (major < REQUIRED_CDS_MAJOR || major === REQUIRED_CDS_MAJOR && minor < REQUIRED_CDS_MINOR) {
55
- const [dkVersion] = require('../package.json').version.split('.')
56
-
57
- console.error(`
58
- This application uses '@sap/cds' version ${major}.${minor}, which is not compatible with the installed '@sap/cds-dk' version ${dkVersion}.
59
- You can either:
60
- - update '@sap/cds' to the latest version (recommended)
61
- - downgrade '@sap/cds-dk' to version ${REQUIRED_CDS_MAJOR}
62
- `)
63
- process.exit(1)
64
- }
65
-
66
66
  function _local (id) {
67
67
  return require.resolve (id, {paths:[
68
68
  process.cwd(), // project-local module is preferred
package/lib/init/add.js CHANGED
@@ -15,5 +15,31 @@ module.exports = {
15
15
  register: (name, plugin) => {
16
16
  registered.set(name, plugin)
17
17
  },
18
+ filterStringAsRegex,
18
19
  registered
19
20
  }
21
+
22
+ /**
23
+ * Turns a filter string with optional meta characters into a regular expression.
24
+ * If no meta characters are found, the string is treated as an include pattern.
25
+ *
26
+ * @param {string} spec the filter string
27
+ * @returns {RegExp} the regular expression
28
+ */
29
+ function filterStringAsRegex(spec) {
30
+ if (typeof spec === 'string') {
31
+ try {
32
+ if (spec.match(/[\^$|*]/)) {
33
+ spec = spec.replace(/^\*/, '.*') // * at start would be invalid, so make it a .*
34
+ return new RegExp(spec, 'i')
35
+ }
36
+ else { // no meta chars -> include semantics
37
+ spec = spec.replace(/\./g, '\\.') // escape literal dot
38
+ return new RegExp(spec, 'i')
39
+ }
40
+ } catch (err) {
41
+ throw err.message // user error, so cut off stack trace
42
+ }
43
+ }
44
+ return /.*/
45
+ }
@@ -5,7 +5,7 @@ module.exports = {
5
5
  COMMAND_ADD: 'add',
6
6
 
7
7
  JAVA_LTS_VERSIONS: [17, 21],
8
- MAVEN_ARCHETYPE_VERSION: '3.4.0',
8
+ MAVEN_ARCHETYPE_VERSION: '3.4.1',
9
9
 
10
10
  OPTIONS: Object.freeze({
11
11
  COMPLETION: 'completion',
@@ -13,9 +13,13 @@ module.exports = {
13
13
  JAVA_MVN: 'java:mvn',
14
14
  JAVA_PACKAGE: 'java:package',
15
15
  NODEJS: 'nodejs',
16
+ ESM: 'esm',
16
17
  SAMPLES: 'samples', // deprecated
17
18
  TINY_SAMPLE: 'tiny-sample',
18
19
  SAMPLE: 'sample',
20
+ TYPER: 'typer',
21
+ TYPESCRIPT: 'typescript',
22
+ HANDLER: 'handler', // influenced by esm, typer and typescript
19
23
  MTA: 'mta',
20
24
  CF_MANIFEST: 'cf-manifest',
21
25
  HELM: 'helm',
@@ -57,8 +61,6 @@ module.exports = {
57
61
  LINT_GLOBAL: 'lint:global',
58
62
  LINT_DEV: 'lint:dev',
59
63
  PIPELINE: 'pipeline',
60
- TYPER: 'typer',
61
- TYPESCRIPT: 'typescript'
62
64
  }),
63
65
 
64
66
  REGEX_JAVA_PACKAGE: /^[a-z_][a-z0-9_]*(\.[a-z_][a-z0-9_]*)*$/g,
@@ -71,9 +73,6 @@ module.exports = {
71
73
  CAPIRE: 'https://cap.cloud.sap',
72
74
  MAVEN_ARCHETYPE_HELP: `https://cap.cloud.sap/docs/java/developing-applications/building#the-maven-archetype`,
73
75
  MAVEN_INSTALL_HELP: `https://maven.apache.org/install.html`,
74
- CAP_JS_COMMUNITY_ODATA_V2: 'https://github.com/cap-js-community/odata-v2-adapter',
75
- CAP_JS_COMMUNITY_EVENT_QUEUE: 'https://github.com/cap-js-community/event-queue',
76
- CAP_JS_COMMUNITY_WEBSOCKET: 'https://github.com/cap-js-community/websocket',
77
76
  CF_CLI: 'https://github.com/cloudfoundry/cli'
78
77
  }),
79
78
 
package/lib/init/index.js CHANGED
@@ -12,17 +12,13 @@ const DEBUG = /\b(y|all|cli)\b/.test(process.env.DEBUG) ? console.debug : undefi
12
12
 
13
13
  module.exports = class CDSGenerator {
14
14
 
15
- /** @returns {string[]} */
16
- static #findPlugins(excluded) {
17
- // actually needs to be sync to be usable in module.exports of dk/bin/init.js
18
- const fromDk = readdirSync(join(__dirname, 'template'))
19
- const fromPlugins = Object.keys(Object.fromEntries(require('./add').registered))
20
- return [...fromDk, ...fromPlugins]
21
- .filter(plugin => !excluded?.includes(plugin))
15
+ constructor() {
16
+ this.cwd = process.cwd()
17
+ this.uiConfig = require('./bas') // Used by CAP Generator
22
18
  }
23
19
 
24
20
  static help({ exclude = [] } = {}) {
25
- if (!cds.add) return // use case shell completion: cds.add is undefined and not needed
21
+ if (!cds.add) return // shell completion: cds.add is undefined and not needed
26
22
 
27
23
  const plugins = this.readPlugins(exclude)
28
24
  const nameFixedLength = Math.max(...plugins.map(plugin => plugin.name.length))
@@ -35,10 +31,14 @@ module.exports = class CDSGenerator {
35
31
  }
36
32
 
37
33
  /**
34
+ * @param excluded {string[]}
38
35
  * @returns {{name: string, module: import('module')}[]}
39
36
  */
40
37
  static readPlugins(excluded = []) {
41
- const plugins = this.#findPlugins(excluded)
38
+ const fromDk = readdirSync(join(__dirname, 'template'))
39
+ const fromPlugins = Object.keys(Object.fromEntries(require('./add').registered))
40
+ const all = [...fromDk, ...fromPlugins]
41
+ .filter(plugin => !excluded?.includes(plugin))
42
42
  .map(plugin => ({
43
43
  name: plugin,
44
44
  module: cds.add.registered.get(plugin) ?? require('./template/' + plugin)
@@ -51,12 +51,7 @@ module.exports = class CDSGenerator {
51
51
  if (b === -1) return -1
52
52
  return a - b
53
53
  }
54
- return plugins.sort(byPriority)
55
- }
56
-
57
- constructor() {
58
- this.cwd = process.cwd()
59
- this.uiConfig = require('./bas') // Used by CAP Generator
54
+ return all.sort(byPriority)
60
55
  }
61
56
 
62
57
  /**
@@ -216,15 +211,14 @@ module.exports = class CDSGenerator {
216
211
  }
217
212
 
218
213
  const cmds = Object.values(OPTIONS)
219
- this.templateList = Array.from(templates.entries())
220
- .sort(([lhs], [rhs]) => {
221
- const a = cmds.indexOf(lhs), b = cmds.indexOf(rhs)
222
- if (a === -1 && b === -1) return 0
223
- if (a === -1) return 1
224
- if (b === -1) return -1
214
+ const priorities = new Map(cmds.map((cmd, i) => [cmd, i]))
215
+ this.templateList = Array.from(templates.keys())
216
+ .sort((lhs, rhs) => {
217
+ const a = priorities.get(lhs) ?? Infinity
218
+ const b = priorities.get(rhs) ?? Infinity
225
219
  return a - b
226
220
  })
227
- .map((entry) => entry[1])
221
+ .map(key => templates.get(key))
228
222
 
229
223
  DEBUG?.(`Features: ${this.templateList.map(({ name }) => name).join(', ')}`);
230
224
  }
@@ -240,11 +234,9 @@ module.exports = class CDSGenerator {
240
234
 
241
235
  const { options } = cds.cli
242
236
  if (typeof options.add === 'string') {
243
- tokens = options.add.split(/[,\s+]/g);
244
-
237
+ tokens = options.add.split(/[,\s+]/g)
245
238
  } else if (Array.isArray(options.add)) {
246
239
  tokens = options.add
247
-
248
240
  } else if (options.add instanceof Set) {
249
241
  tokens = [...options.add];
250
242
  }
package/lib/init/merge.js CHANGED
@@ -6,11 +6,8 @@ const { readYAML, writeYAML, readJSON } = require('../util/fs')
6
6
  const { detectIndent } = require('./indent')
7
7
  const { colors } = require('../util/term')
8
8
 
9
- function _isObject(item) {
10
- return item && typeof item === 'object' && !Array.isArray(item)
11
- }
12
-
13
9
  function _mergeObject(target, source) {
10
+ const _isObject = item => item && typeof item === 'object' && !Array.isArray(item)
14
11
  const unique = array => [...new Set(array.map(JSON.stringify))].map(JSON.parse)
15
12
  if (_isObject(target) && _isObject(source)) {
16
13
  for (const key in source) {