@sap/cds-dk 8.6.1 → 8.7.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 (53) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/bin/cds.js +1 -1
  3. package/bin/debug.js +122 -26
  4. package/bin/deploy/index.js +7 -0
  5. package/bin/import.js +54 -29
  6. package/bin/repl.js +2 -2
  7. package/bin/util/fuzzySearch.js +10 -10
  8. package/bin/watch.js +46 -16
  9. package/lib/build/buildTaskProviderFactory.js +8 -5
  10. package/lib/build/plugins/helm/files/subcharts/content-deployment/Chart.yaml +1 -1
  11. package/lib/build/plugins/helm/files/subcharts/content-deployment/templates/job.yaml +3 -2
  12. package/lib/build/plugins/helm/files/subcharts/content-deployment/values.yaml +1 -1
  13. package/lib/build/plugins/helm/files/subcharts/web-application/Chart.yaml +1 -1
  14. package/lib/build/plugins/helm/files/subcharts/web-application/templates/_helpers.tpl +3 -3
  15. package/lib/build/plugins/helm/files/subcharts/web-application/templates/api-rule.yaml +1 -1
  16. package/lib/build/plugins/helm/files/subcharts/web-application/templates/deployment.yaml +13 -5
  17. package/lib/build/plugins/helm/files/subcharts/web-application/templates/service-binding.yaml +1 -0
  18. package/lib/build/plugins/helm/files/subcharts/web-application/values.schema.json +28 -15
  19. package/lib/build/plugins/helm/files/subcharts/web-application/values.yaml +22 -1
  20. package/lib/import/common.js +174 -44
  21. package/lib/import/importapi.js +1 -1
  22. package/lib/import/message.js +1 -1
  23. package/lib/import/odata/ParserContext.js +46 -0
  24. package/lib/import/odata/index.js +20 -11
  25. package/lib/import/odata/v4parser.js +25 -51
  26. package/lib/init/add.js +0 -2
  27. package/lib/init/constants.js +3 -3
  28. package/lib/init/index.js +1 -1
  29. package/lib/init/merge.js +67 -66
  30. package/lib/init/mvn.js +16 -12
  31. package/lib/init/projectReader.js +2 -2
  32. package/lib/init/template/approuter/files/mta.yaml.hbs +2 -4
  33. package/lib/init/template/hana/index.js +12 -8
  34. package/lib/init/template/ias/files/mta.yaml.hbs +15 -0
  35. package/lib/init/template/ias/index.js +22 -5
  36. package/lib/init/template/mta/files/mta.yaml.hbs +3 -13
  37. package/lib/init/template/multitenancy/files/mta.yaml.hbs +1 -0
  38. package/lib/init/template/multitenancy/index.js +5 -0
  39. package/lib/init/template/nodejs/files/package.json.hbs +1 -1
  40. package/lib/init/template/portal/files/portal-site/i18n/catalog.properties +1 -1
  41. package/lib/init/template/portal/files/portal-site/i18n/catalog_de.properties +1 -1
  42. package/lib/init/template/portal/files/portal-site/i18n/group.properties +1 -1
  43. package/lib/init/template/portal/files/portal-site/i18n/group_de.properties +1 -1
  44. package/lib/init/template/sample/files/typescript/admin-service.ts.hbs +16 -0
  45. package/lib/init/template/sample/files/typescript/cat-service.ts.hbs +38 -0
  46. package/lib/init/template/sample/index.js +16 -12
  47. package/lib/init/template/typescript/files/package.json +1 -1
  48. package/lib/lint/io.js +6 -16
  49. package/lib/watch/node-watch.js +3 -3
  50. package/npm-shrinkwrap.json +49 -33
  51. package/package.json +1 -1
  52. /package/lib/init/template/sample/files/{nodejs → javascript}/admin-service.js.hbs +0 -0
  53. /package/lib/init/template/sample/files/{nodejs → javascript}/cat-service.js.hbs +0 -0
@@ -7,7 +7,7 @@
7
7
  const messages = require("../message").getMessages();
8
8
  const common = require("../common");
9
9
  const versionInfo = require("../../../package.json").version;
10
- const Multimap = require("./util/MultiMap");
10
+ const ParserContext = require("./ParserContext");
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");
@@ -82,30 +82,7 @@ const known_vocabularies = {
82
82
  'com.sap.vocabularies.UI.v1': 'UI',
83
83
  };
84
84
 
85
- function _initialize(parserContext) {
86
- parserContext.unboundActions = {};
87
- parserContext.unboundFunctions = {};
88
- parserContext.entityTypes = [];
89
- parserContext.entityTypeOpenEntries = [];
90
- parserContext.complexTypeOpenEntries = [];
91
- parserContext.complexTypes = [];
92
- parserContext.enumTypes = [];
93
- parserContext.typeDefinitions = [];
94
- parserContext.entityContainerName = "";
95
- parserContext.entityToEntitySetMap = new Multimap();
96
- parserContext.entityToSingletonMap = new Multimap();
97
- parserContext.actionToActionImportMap = new Multimap();
98
- parserContext.functionToFunctionImportMap = new Multimap();
99
- parserContext.primarySchema = "";
100
- parserContext.schemaAliasToNamespace = {};
101
- parserContext.schemaToSchemaDataIndex = {};
102
- parserContext.vocabNamespaceToAlias = {};
103
- parserContext.entityToBoundActions = {};
104
- parserContext.entityToBoundFunctions = {};
105
- parserContext.entityContainerAnnotations = {};
106
- parserContext.mockServerUc = true;
107
- parserContext.beta = false;
108
- }
85
+
109
86
  function _isJson(edmx) {
110
87
  let isJson = false;
111
88
  try {
@@ -291,7 +268,7 @@ function _replaceSpecialCharacters(text) {
291
268
  return text.replace(/(?:\\[rn]|[\r\n]+)+/gm, "\n").replace(/\s+/g, ' ').replace(/"/g, """).trim();
292
269
  }
293
270
 
294
- function _resolveCoreTerm(annotationTerm, annotationTermObject, csnDefsTargetObject, parserContext) {
271
+ function _resolveCoreTerm(annotationTerm, annotationTermObject, csnDefsTargetObject) {
295
272
  if (annotationTerm === "@Core.Description" || annotationTerm === "@Core.LongDescription") {
296
273
  // we don't capture `null` value for doc
297
274
  if (annotationTermObject === null) return;
@@ -314,9 +291,7 @@ function _resolveCoreTerm(annotationTerm, annotationTermObject, csnDefsTargetObj
314
291
  if (annotationTermObject === null) annotationTermObject = true;
315
292
 
316
293
  if (annotationTerm === "@Core.OptionalParameter") {
317
- if (parserContext.beta) {
318
- _updateElementObjectForDefaultValue(csnDefsTargetObject, annotationTermObject.DefaultValue);
319
- }
294
+ _updateElementObjectForDefaultValue(csnDefsTargetObject, annotationTermObject.DefaultValue);
320
295
  } else {
321
296
  _captureAnnotationInCSN(annotationTerm, annotationTermObject, csnDefsTargetObject);
322
297
  }
@@ -405,13 +380,13 @@ function _parseValidationTerm(annotationTerm, annotationTermObject, csnDefsTarge
405
380
  _captureAnnotationInCSN(annotationTerm, annotationTermObject, csnDefsTargetObject);
406
381
  }
407
382
 
408
- function _parseAnnotationTerms(annotationTerm, annotationTermObject, csnDefsTargetObject, parserContext) {
383
+ function _parseAnnotationTerms(annotationTerm, annotationTermObject, csnDefsTargetObject) {
409
384
  const term = annotationTerm.split('.');
410
385
  const vocabAlias = term[0];
411
386
 
412
387
  switch (vocabAlias) {
413
388
  case "@Core":
414
- _resolveCoreTerm(annotationTerm, annotationTermObject, csnDefsTargetObject, parserContext);
389
+ _resolveCoreTerm(annotationTerm, annotationTermObject, csnDefsTargetObject);
415
390
  break;
416
391
  case "@Capabilities":
417
392
  _parseCapabilitiesTerm(annotationTerm, annotationTermObject, csnDefsTargetObject);
@@ -477,13 +452,12 @@ function _resolveAnnotationTerm(schemaDataAnnotation, parserContext) {
477
452
  }
478
453
  }
479
454
 
480
- let index = 0;
481
- while (schemaDataAnnotation.indexOf('@', index) != -1) {
482
-
483
- let i = schemaDataAnnotation.indexOf('@', index);
484
- let annotationTerm = schemaDataAnnotation.substring(i, schemaDataAnnotation.indexOf('"', i));
455
+ let index = schemaDataAnnotation.indexOf('"@');
456
+ while (index != -1) {
457
+ let i = schemaDataAnnotation.indexOf('"', index + 1);
458
+ let annotationTerm = schemaDataAnnotation.substring(index+1, i);
485
459
  _validateAnnotationNamespace(annotationTerm, parserContext.vocabNamespaceToAlias);
486
- index = i+1;
460
+ index = schemaDataAnnotation.indexOf('"@', i+1);
487
461
  }
488
462
 
489
463
  return JSON.parse(schemaDataAnnotation);
@@ -504,7 +478,7 @@ function _captureAnnotationWithinElement(sourceObject, targetObject, parserConte
504
478
  Object.keys(sourceObject).forEach(key => {
505
479
  if (key.startsWith('@')) {
506
480
  element = _resolveAnnotationTerm(JSON.stringify(key), parserContext);
507
- _parseAnnotationTerms(element, sourceObject[key], targetObject, parserContext);
481
+ _parseAnnotationTerms(element, sourceObject[key], targetObject);
508
482
  }
509
483
  });
510
484
  }
@@ -643,6 +617,12 @@ function _parseAssociationProperty(foreignReference, associationNode, parserCont
643
617
  const associationProperty = {};
644
618
  associationProperty.type = (associationNode.$OnDelete === 'Cascade') ? 'cds.Composition' : 'cds.Association';
645
619
 
620
+ let schemaName = associationNode.$Type.substring(0, associationNode.$Type.lastIndexOf('.'));
621
+
622
+ if (parserContext.schemaAliasToNamespace[schemaName]) {
623
+ schemaName = parserContext.schemaAliasToNamespace[schemaName];
624
+ }
625
+
646
626
  let resolvedEntityName;
647
627
  let entityTypeMapping = (
648
628
  parserContext.entityToEntitySetMap?.get(associationNode.$Type) ||
@@ -661,11 +641,11 @@ function _parseAssociationProperty(foreignReference, associationNode, parserCont
661
641
  if (entityTypeMapping.length > 1) {
662
642
  for (let j = 0; j < entityTypeMapping.length; j++) {
663
643
  if (entityTypeMapping[j] === foreignReference) {
664
- associationProperty.target = parserContext.primarySchema + '.' + entityTypeMapping[j];
644
+ associationProperty.target = schemaName + '.' + entityTypeMapping[j];
665
645
  }
666
646
  }
667
647
  } else {
668
- associationProperty.target = parserContext.primarySchema + '.' + entityTypeMapping;
648
+ associationProperty.target = schemaName + '.' + entityTypeMapping;
669
649
  }
670
650
  } else {
671
651
  associationProperty.target = resolvedEntityName;
@@ -1219,7 +1199,7 @@ function _parseAnnotations(schemaAnnotations, csnDefs, parserContext) {
1219
1199
  let annotationTerms = schemaAnnotations[annotationTargetPath];
1220
1200
  Object.keys(annotationTerms).forEach(annotationTerm => {
1221
1201
  let annotationTermObject = schemaAnnotations[annotationTargetPath][annotationTerm];
1222
- _parseAnnotationTerms(annotationTerm, annotationTermObject, csnDefsTargetObject, parserContext);
1202
+ _parseAnnotationTerms(annotationTerm, annotationTermObject, csnDefsTargetObject);
1223
1203
  });
1224
1204
  }
1225
1205
  });
@@ -1673,10 +1653,10 @@ function _generateEDMX2JSON(edmx, name_space) {
1673
1653
  console.log(missingReferencedDocuments);
1674
1654
  }
1675
1655
 
1676
- if (error) {
1677
- reject(error);
1678
- } else {
1656
+ if (json) {
1679
1657
  resolve(json);
1658
+ } else {
1659
+ reject(error);
1680
1660
  }
1681
1661
  });
1682
1662
  } catch (err) {
@@ -1690,13 +1670,7 @@ async function getEdmxv4CSN(edmx, ignorePersistenceSkip, mockServerUc, importerC
1690
1670
  let edmjConverted;
1691
1671
  let name_space = [];
1692
1672
 
1693
- const parserContext = {};
1694
- _initialize(parserContext);
1695
- parserContext.mockServerUc = mockServerUc;
1696
-
1697
- if (importerContext.beta) {
1698
- parserContext.beta = importerContext.beta
1699
- }
1673
+ const parserContext = ParserContext.getInstance(mockServerUc);
1700
1674
 
1701
1675
  if (importerContext.include_all_namespaces) {
1702
1676
  name_space.push(true);
package/lib/init/add.js CHANGED
@@ -3,8 +3,6 @@ const registered = new Map
3
3
  module.exports = {
4
4
  Plugin: require('./plugin'),
5
5
  merge: require('./merge').merge, // fluent API
6
- removeFromYAML: require('./merge').removeFromYAML,
7
- removeFromYAMLArray: require('./merge').removeFromYAMLArray,
8
6
  readProject: require('./projectReader').readProject,
9
7
  mvn: {
10
8
  add: require('./mvn').add
@@ -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.6.0',
8
+ MAVEN_ARCHETYPE_VERSION: '3.7.0',
9
9
 
10
10
  OPTIONS: Object.freeze({
11
11
  COMPLETION: 'completion',
@@ -14,11 +14,11 @@ module.exports = {
14
14
  JAVA_PACKAGE: 'java:package',
15
15
  NODEJS: 'nodejs',
16
16
  ESM: 'esm',
17
+ TYPER: 'typer',
18
+ TYPESCRIPT: 'typescript', // must run before SAMPLE, as that influences whether .js or .ts files are created
17
19
  SAMPLES: 'samples', // deprecated
18
20
  TINY_SAMPLE: 'tiny-sample',
19
21
  SAMPLE: 'sample',
20
- TYPER: 'typer',
21
- TYPESCRIPT: 'typescript',
22
22
  HANDLER: 'handler', // influenced by esm, typer and typescript
23
23
  MTA: 'mta',
24
24
  CF_MANIFEST: 'cf-manifest',
package/lib/init/index.js CHANGED
@@ -181,7 +181,7 @@ module.exports = class CDSGenerator {
181
181
  if (err.code === 'MODULE_NOT_FOUND') {
182
182
  const entries = this.constructor.readPlugins()
183
183
  const fuzzySearch = require('../../bin/util/fuzzySearch')
184
- const [bestMatch] = fuzzySearch(plugin, entries.map(e => e.name))
184
+ const [bestMatch] = fuzzySearch(plugin, entries.map(e => e.name), null, { maxListLength: entries.length })
185
185
  const max = entries.reduce((max, {name}) => Math.max(max, name.length), 0)
186
186
  const allFacetsText = entries.filter(({module})=> module.help()).map(({name, module}) => {
187
187
  const help = module.help?.() ?? ''
package/lib/init/merge.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const { EOL } = require('node:os')
2
2
  const { inspect } = require('node:util')
3
3
  const cds = require('../cds')
4
- const { copy, read, write, exists, path, yaml: YAML } = cds.utils
4
+ const { copy, read, write, exists, path: {resolve, join}, yaml: YAML } = cds.utils
5
5
  const { readYAML, writeYAML, readJSON } = require('../util/fs')
6
6
  const { detectIndent } = require('./indent')
7
7
  const { colors } = require('../util/term')
@@ -29,7 +29,7 @@ function _mergeObject(target, source) {
29
29
  module.exports.sort = async function(path, key) {
30
30
  const json = await read(path)
31
31
  json[key] = Object.fromEntries(
32
- Object.entries(json[key]).sort(([a],[b]) => a>b ? 1 : -1)
32
+ Object.entries(json[key] ?? {}).sort(([a],[b]) => a>b ? 1 : -1)
33
33
  )
34
34
  await write(path, json, { spaces: 2 });
35
35
  }
@@ -206,6 +206,10 @@ async function _mergeYAML(into, from, project, semantics = {}) {
206
206
  } else {
207
207
  _traverseYAMLCollection(node, actions, templateNode)
208
208
  }
209
+ } else if(node.value?.source === '' && YAML.isSeq(templateNode?.value)) {
210
+ node.value = new YAML.YAMLSeq()
211
+ collectionStack.unshift(node.value)
212
+ _traverseYAMLNode(node.value, index, actions, templateNode?.value)
209
213
  } else if (node.value?.items || node.value && semantics.forceOverwrite) {
210
214
  _traverseYAMLNode(node.value, index, actions, templateNode?.value)
211
215
  }
@@ -374,75 +378,72 @@ async function _mergeYAML(into, from, project, semantics = {}) {
374
378
  return target
375
379
  }
376
380
 
377
- module.exports.removeFromYAML = async function (name, keyPaths) {
378
- const yaml = await readYAML(name)
379
- for (const keyPath of keyPaths) {
380
- let node = yaml
381
- const keys = keyPath.split('.')
382
- for (const [index, key] of keys.entries()) {
383
- if (index === keys.length - 1) {
384
- node.delete(key)
385
- } else {
386
- node = node.get(key)
387
- if(node === undefined) break;
388
- }
389
- }
390
- }
391
- await writeYAML(name, yaml)
392
- }
393
-
394
- module.exports.removeFromYAMLArray = async function(name, keyPath, aliasToRemove) {
395
- const yaml = await readYAML(name)
396
-
397
- let node = yaml
398
- // traverse to the array node
399
- const keys = keyPath.split('.')
400
- for (const key of keys) {
401
- node = node.get(key)
402
- }
403
-
404
- // find index of array entry with aliasToRemove entry
405
- let index = 0;
406
- let entryNode = node.getIn([index])
407
- while(entryNode !== undefined) {
408
- if(entryNode.toJSON().alias === aliasToRemove) {
409
- node.deleteIn([index])
410
- break;
411
- }
412
- index++;
413
- entryNode = node.getIn([index])
414
- }
415
-
416
- await writeYAML(name, yaml)
417
- }
418
-
419
381
  /**
420
382
  * @param {...(string | Object | YAML.Document)} src The source to merge from.
421
383
  * @returns {{
422
- * into: (dst: (string | Object | YAML.Document), options?: {project?: Object, semantics?: MergingSemantics}) => Promise<any>
423
- * }} An object with an 'into' method to merge the sources into the specified destination.
424
- */
384
+ * into: (dst: (string | Object | YAML.Document), options?: {project?: Object, semantics?: MergingSemantics}) => Promise<any>,
385
+ * remove: (items: (string | object)[]) => Promise<void>
386
+ * }} An object with an 'into' method to merge the sources into the specified destination.
387
+ */
425
388
  module.exports.merge = (...src) => {
426
- return {
427
- /**
428
- * Merges sources into the specified destination.
429
- * @param {(string | Object | YAML.Document)} dst File path or object to merge into.
430
- * @param {Object} [options] Merging options.
431
- * @param {Object} [options.project] Project descriptor used for Mustache replacements.
432
- * @param {MergingSemantics} [options.semantics] Merging algorithm semantics.
433
- * @returns {Promise} A promise resolving to the merged object.
434
- */
435
- into: (dst, { with: withProject, project, ...semantics } = {}) => {
436
- if (Object.keys(semantics).length === 0) semantics = undefined
437
- project ??= withProject // to allow { with: project } for a more fluent API
438
- const isYAML = typeof dst === 'string' && ['.yaml', '.yml', '.yaml.hbs', '.yml.hbs'].some(ext => dst.endsWith(ext))
439
- const _merge = isYAML ? _mergeYAML : _mergeJSON
440
- const target = typeof dst === 'string' ? path.resolve(cds.root, dst) : dst
441
- if (typeof src[src.length-1] === 'string') {
442
- return _merge(target, path.join(...src), project, semantics)
443
- } else {
444
- return _merge(target, ...src, project, semantics)
389
+ return {
390
+ /**
391
+ * Merges sources into the specified destination.
392
+ * @param {(string | Object | YAML.Document)} dst File path or object to merge into.
393
+ * @param {Object} [options] Merging options.
394
+ * @param {Object} [options.project] Project descriptor used for Mustache replacements.
395
+ * @param {MergingSemantics} [options.semantics] Merging algorithm semantics.
396
+ * @returns {Promise} A promise resolving to the merged object.
397
+ */
398
+ into: (dst, { with: withProject, project, ...semantics } = {}) => {
399
+ if (Object.keys(semantics).length === 0) semantics = undefined
400
+ project ??= withProject // to allow { with: project } for a more fluent API
401
+ const isYAML = typeof dst === 'string' && ['.yaml', '.yml', '.yaml.hbs', '.yml.hbs'].some(ext => dst.endsWith(ext))
402
+ const _merge = isYAML ? _mergeYAML : _mergeJSON
403
+ const target = typeof dst === 'string' ? resolve(cds.root, dst) : dst
404
+ if (typeof src[src.length - 1] === 'string') {
405
+ return _merge(target, join(...src), project, semantics)
406
+ } else {
407
+ return _merge(target, ...src, project, semantics)
408
+ }
409
+ },
410
+ /**
411
+ * Removes items from the specified YAML file.
412
+ * @param {(string | object)[]} items An array of dotted‐path strings or objects to remove.
413
+ * @returns {Promise<void>} A promise that resolves once the file is updated.
414
+ */
415
+ remove: async items => {
416
+ const yaml = await readYAML(resolve(cds.root, ...src))
417
+ for (const item of items) {
418
+ if (typeof item === 'string') {
419
+ const keys = item.split('.')
420
+ let node = yaml
421
+ for (let i = 0; i < keys.length - 1; i++) {
422
+ node = node.get(keys[i])
423
+ if (node === undefined) break
424
+ }
425
+ node?.delete?.(keys[keys.length - 1])
426
+ } else if (item && typeof item === 'object') {
427
+ const stack = [{ node: yaml, obj: item }]
428
+ while (stack.length) {
429
+ const { node, obj } = stack.pop()
430
+ for (const [k, v] of Object.entries(obj)) {
431
+ const child = node.get(k)
432
+ if (YAML.isSeq(child)) {
433
+ child.items = child.items.filter(x => {
434
+ if (YAML.isMap(x)) {
435
+ return !Object.entries(v).every(([kk, vv]) => x.get(kk) === vv)
436
+ }
437
+ return true
438
+ })
439
+ } else if (YAML.isMap(child)) {
440
+ stack.push({ node: child, obj: v })
441
+ }
445
442
  }
443
+ }
446
444
  }
445
+ await writeYAML(join(...src), yaml)
446
+ }
447
447
  }
448
+ }
448
449
  }
package/lib/init/mvn.js CHANGED
@@ -10,25 +10,29 @@ const LOG = cds.log('cli|init|add|mvn'), DEBUG = cds.debug('cli|init|add|mvn')
10
10
 
11
11
  const { URLS, MAVEN_ARCHETYPE_VERSION, OPTIONS: { JAVA_PACKAGE, HANA }, REGEX_JAVA_PACKAGE } = require('./constants')
12
12
 
13
- module.exports.add = async function (feature) {
13
+ module.exports.add = async function (feature, featureOptions={}) {
14
14
  const params = new Map()
15
+ let archetypeVersion = MAVEN_ARCHETYPE_VERSION
16
+
17
+ params.set(`com.sap.cds:cds-maven-plugin:${archetypeVersion}:add`)
15
18
 
16
- const { 'java:mvn': javaMvn, force } = cds.cli.options
17
- if (javaMvn) _scan(javaMvn).forEach(([k,v]) => params.set(k,v))
18
- const archetypeVersion = params.get('-DarchetypeVersion') ?? MAVEN_ARCHETYPE_VERSION
19
19
  if (feature === HANA) { // REVISIT: compat for HANA until -Dfeature HANA is provided by Java
20
- params.set(`com.sap.cds:cds-maven-plugin:${archetypeVersion}:add`)
21
- params.set(`-Dfeature`, `CF`) // REVISIT: Should get rid of CF dependency for Kyma scenarios
22
- } else {
23
- params.set(`com.sap.cds:cds-maven-plugin:${archetypeVersion}:add`)
24
- params.set(`-Dfeature`, feature)
25
- const profile = cds.cli.options.for
26
- params.set(`-Dprofile`, profile === 'production' ? 'cloud' : profile ?? 'default')
20
+ feature = `CF` // REVISIT: Should get rid of CF dependency for Kyma scenarios
27
21
  }
22
+ params.set(`-Dfeature`, feature)
23
+
24
+ const profile = cds.cli.options.for
25
+ params.set(`-Dprofile`, profile === 'production' ? 'cloud' : profile ?? 'default')
28
26
 
27
+ Object.entries(featureOptions).forEach(([k,v]) => params.set(`-Doptions.${k}`, v))
28
+
29
+ // evaluate last to allow generic override from CLI
30
+ const { 'java:mvn': javaMvn, force } = cds.cli.options
31
+ if (javaMvn) _scan(javaMvn).forEach(([k,v]) => params.set(k,v))
29
32
  if (force) params.set(`-Doverwrite`, true)
33
+
30
34
  try {
31
- await cmd.spawnCommand('mvn', _finalize(params), { cwd: cds.root })
35
+ return await cmd.spawnCommand('mvn', _finalize(params), { cwd: cds.root }, false, true)
32
36
  } catch (err) {
33
37
  if (err.code === 'ENOENT' && err.path === 'mvn') {
34
38
  throw `Maven executable 'mvn' not found. Follow ${term.info(URLS.MAVEN_INSTALL_HELP)} and install Maven on your machine.`
@@ -62,7 +62,7 @@ module.exports = new class ProjectReader {
62
62
  * @property {string} appName The name of the application.
63
63
  * @property {string} appId The ID of the application.
64
64
  * @property {string} appDescription The description of the application.
65
- * @property {string} language The programming language of the project.
65
+ * @property {'java' | 'nodejs'} language The programming language of the project.
66
66
  * @property {string} srvPath The path to the server module.
67
67
  * @property {string} archiveName The name of the archive.
68
68
  * @property {Object} db Database configuration.
@@ -120,7 +120,6 @@ module.exports = new class ProjectReader {
120
120
  const _isESM = () => pkgJson().type === 'module'
121
121
  const _isTypescript = () => !!pkgJson().dependencies?.typescript || !!pkgJson().devDependencies?.typescript
122
122
  const _hasTyper = () => !!pkgJson().dependencies?.['@cap-js/cds-typer'] || !!pkgJson().devDependencies?.['@cap-js/cds-typer']
123
-
124
123
  const _inProd = plugin => require(`./template/${plugin}`).hasInProduction(env)
125
124
  const _ui5 = () => pkgJson().sapux
126
125
  const _uiEmbedded = () => exists(join(env.folders.app, 'xs-app.json'))
@@ -128,6 +127,7 @@ module.exports = new class ProjectReader {
128
127
  const _isJava = () => this.isJava
129
128
  const isApp = name => name !== 'appconfig' && !name.startsWith('_') && name !== 'router' && name !== 'portal'
130
129
  const reserved = {
130
+ dependencies: () => pkgJson().dependencies,
131
131
  has: x => _inProd(x),
132
132
  profile: () => cds.cli.options?.for,
133
133
  language: () => _isJava() ? 'java' : 'nodejs',
@@ -33,10 +33,8 @@ modules:
33
33
  group: destinations
34
34
  properties:
35
35
  name: srv-api # must be used in xs-app.json as well
36
- url: {{#hasIas}}~{srv-cert-url}{{/hasIas}}{{^hasIas}}~{srv-url}{{/hasIas}}
37
- forwardAuthToken: true{{#hasIas}}
38
- forwardAuthCertificates: true
39
- strictSSL: true{{/hasIas}}
36
+ url: ~{srv-url}
37
+ forwardAuthToken: true
40
38
  {{#hasMultitenancy}}
41
39
  - name: mtx-api
42
40
  group: destinations
@@ -2,7 +2,7 @@ const { join } = require('path')
2
2
  const cds = require('../../../cds')
3
3
  const { read, write, exists, copy, rimraf } = cds.utils
4
4
  const { readProject } = require('../../projectReader')
5
- const { merge, removeFromYAML, removeFromYAMLArray } = require('../../merge')
5
+ const { merge } = require('../../merge')
6
6
  const mvn = require('../../mvn')
7
7
 
8
8
  const { srv4, hdbDeployer, serviceManager, hdiContainer, mtxSidecar4 } = require('../../registries/mta')
@@ -14,7 +14,7 @@ module.exports = class HanaTemplate extends require('../../plugin') {
14
14
  }
15
15
 
16
16
  static hasInProduction(env) {
17
- return env.requires?.db?.kind === 'hana' || env.dependencies?.['@cap-js/hana']
17
+ return env.requires?.db?.kind === 'hana' || readProject().dependencies?.['@cap-js/hana']
18
18
  }
19
19
 
20
20
  async run() {
@@ -79,17 +79,21 @@ module.exports = class HanaTemplate extends require('../../plugin') {
79
79
  }
80
80
 
81
81
  const modules = hasMultitenancy ? [srv] : [srv, hdbDeployer]
82
-
83
- await merge(__dirname, 'files/mta.yaml.hbs').into('mta.yaml', { with: project,
84
- additions: [...modules, db].filter(a => a), deletions, relationships
82
+ await merge(__dirname, 'files/mta.yaml.hbs').into('mta.yaml', {
83
+ with: project, additions: [...modules, db].filter(a => a), deletions, relationships
85
84
  })
86
85
  }
87
86
 
88
87
  if (hasHelm || hasHelmUnifiedRuntime) {
89
88
  if (hasMultitenancy) {
90
- await removeFromYAML(join('chart', 'values.yaml'), ['hana-deployer', 'srv.bindings.db', 'hana'])
91
- await removeFromYAMLArray(join('chart', 'Chart.yaml'), 'dependencies', 'hana')
92
- await removeFromYAMLArray(join('chart', 'Chart.yaml'), 'dependencies', 'hana-deployer')
89
+ await merge('chart/values.yaml')
90
+ .remove(['hana-deployer', 'srv.bindings.db', 'hana'])
91
+
92
+ await merge('chart/Chart.yaml')
93
+ .remove([
94
+ { dependencies: { alias: 'hana' } },
95
+ { dependencies: { alias: 'hana-deployer' } }
96
+ ])
93
97
 
94
98
  if (!hasHtml5Repo) {
95
99
  await rimraf('chart/charts/content-deployment') // REVISIT: Move to build task
@@ -1,3 +1,4 @@
1
+ ---
1
2
  modules:
2
3
  - name: {{appName}}-srv
3
4
  type: {{language}}
@@ -8,6 +9,14 @@ modules:
8
9
  config:
9
10
  credential-type: X509_GENERATED
10
11
  app-identifier: srv
12
+ parameters:
13
+ routes:
14
+ - route: "${default-url}"
15
+ - route: "${default-host}.cert.${default-domain}"
16
+ provides:
17
+ - name: srv-api
18
+ properties:
19
+ srv-cert-url: '${protocol}://${default-host}.cert.${default-domain}'
11
20
 
12
21
  - name: {{appName}}
13
22
  type: approuter.nodejs
@@ -18,6 +27,12 @@ modules:
18
27
  config:
19
28
  credential-type: X509_GENERATED
20
29
  app-identifier: approuter
30
+ - name: srv-api
31
+ group: destinations
32
+ properties:
33
+ url: ~{srv-cert-url}
34
+ forwardAuthCertificates: true
35
+ strictSSL: true
21
36
 
22
37
  - name: {{appName}}-mtx
23
38
  type: nodejs
@@ -2,6 +2,7 @@ const { readProject } = require('../../projectReader')
2
2
  const { merge } = require('../../merge')
3
3
  const { srv4, approuter, mtxSidecar4 } = require('../../registries/mta')
4
4
  const cds = require('../../../cds')
5
+ const { read, write } = cds.utils
5
6
  // const mvn = require('../../mvn')
6
7
 
7
8
  module.exports = class IasTemplate extends require('../../plugin') {
@@ -31,21 +32,37 @@ module.exports = class IasTemplate extends require('../../plugin') {
31
32
  const project = readProject()
32
33
  const { isJava, hasMta, hasCfManifest, hasHelm, hasHelmUnifiedRuntime, hasApprouter, hasMultitenancy, srvPath } = project
33
34
 
35
+ if (hasMultitenancy) {
36
+ const conf = (await read('mtx/sidecar/package.json')).cds ?? {}
37
+ if (conf.profile) delete conf.profile
38
+ conf.profiles ??= conf.profile ? [conf.profile] : []
39
+ if (!conf.profiles.includes('subscription-manager')) {
40
+ conf.profiles.push('subscription-manager')
41
+ }
42
+ await write('mtx/sidecar/package.json', conf, { spaces: 2 })
43
+ }
44
+
34
45
  if (hasMta) {
35
46
  const srv = srv4(srvPath)
36
47
  const identity = { in: 'resources', where: { 'parameters.service': 'identity' } }
37
48
  const srvIdentityBinding = { in: [srv, 'requires'], where: { 'parameters.config.app-identifier': 'srv' } }
38
- const additions = [srv, identity, srvIdentityBinding]
49
+ const srvCertUrlApi = { in: [srv, 'provides'], where: { 'name': 'srv-api' } }
50
+ const additions = [srv, identity, srvIdentityBinding, srvCertUrlApi]
51
+ const overwrites = []
52
+
39
53
  if (hasApprouter) {
40
- const approuterIdentityBinding = { in: [approuter, 'requires'], where: { 'parameters.config.app-identifier': 'approuter' } }
41
- additions.push(approuter, approuterIdentityBinding)
54
+ additions.push(approuter)
55
+ additions.push({ in: [approuter, 'requires'], where: { 'parameters.config.app-identifier': 'approuter' }})
56
+ const srvApi = { in: [approuter, 'requires'], where: { 'name': 'srv-api' } }
57
+ additions.push(srvApi)
58
+ overwrites.push({ item: [srvApi, 'properties.url'], withValue: '~{srv-cert-url}' })
42
59
  }
43
60
  if (hasMultitenancy) {
44
61
  const mtxSidecar = mtxSidecar4(isJava ? 'mtx/sidecar' : 'gen/mtx/sidecar')
45
62
  const mtxIdentityBinding = { in: [mtxSidecar, 'requires'], where: { 'parameters.config.app-identifier': 'mtx' } }
46
63
  additions.push(mtxSidecar, mtxIdentityBinding)
47
64
  }
48
- await merge(__dirname, 'files/mta.yaml.hbs').into('mta.yaml', { project, additions })
65
+ await merge(__dirname, 'files/mta.yaml.hbs').into('mta.yaml', { project, additions, overwrites })
49
66
  }
50
67
 
51
68
  if (hasHelm || hasHelmUnifiedRuntime) {
@@ -62,7 +79,7 @@ module.exports = class IasTemplate extends require('../../plugin') {
62
79
 
63
80
  async #combineWithCfManifest(project) {
64
81
  const srv = { in: 'applications', where: { 'name': `${project.appName}-srv` } }
65
- const identityBinding = { in: 'services', where: { 'parameters.app-identifier': project.appName } }
82
+ const identityBinding = { in: 'services', where: { 'parameters.app-identifier': 'srv' } }
66
83
  await merge(__dirname, 'files/manifest.yml.hbs').into('manifest.yml', { project, additions: [srv, identityBinding] })
67
84
 
68
85
  const identity = { in: 'create-services', where: { 'broker': 'identity' } }
@@ -19,27 +19,18 @@ modules:
19
19
  path: {{& srvPath}}
20
20
  {{#isNodejs}}
21
21
  parameters:
22
+ instances: 1
22
23
  buildpack: nodejs_buildpack
23
24
  readiness-health-check-type: http
24
25
  readiness-health-check-http-endpoint: /health
25
- {{#hasIas}}
26
- routes:
27
- - route: "${default-url}"
28
- - route: "${default-host}.cert.${default-domain}"
29
- {{/hasIas}}
30
26
  build-parameters:
31
- builder: npm
27
+ builder: npm-ci
32
28
  {{/isNodejs}}
33
29
  {{#isJava}}
34
30
  parameters:
35
31
  buildpack: sap_java_buildpack_jakarta
36
32
  readiness-health-check-type: http
37
33
  readiness-health-check-http-endpoint: /actuator/health/readiness
38
- {{#hasIas}}
39
- routes:
40
- - route: "${default-url}"
41
- - route: "${default-host}.cert.${default-domain}"
42
- {{/hasIas}}
43
34
  properties:
44
35
  SPRING_PROFILES_ACTIVE: cloud,sandbox
45
36
  JBP_CONFIG_COMPONENTS: "jres: ['com.sap.xs.java.buildpack.jre.SAPMachineJRE']"
@@ -53,8 +44,7 @@ modules:
53
44
  provides:
54
45
  - name: srv-api # required by consumers of CAP services (e.g. approuter)
55
46
  properties:
56
- srv-url: ${default-url}{{#hasIas}}
57
- srv-cert-url: '${protocol}://${default-host}.cert.${default-domain}'{{/hasIas}}
47
+ srv-url: ${default-url}
58
48
  requires: []
59
49
 
60
50
  resources:
@@ -33,6 +33,7 @@ modules:
33
33
  []
34
34
  {{/isJava}}
35
35
  parameters:
36
+ instances: 1
36
37
  memory: 256M
37
38
  disk-quota: 512M{{#hasIas}}
38
39
  routes:
@@ -24,6 +24,11 @@ module.exports = class MultitenancyTemplate extends require('../../plugin') {
24
24
  return options?.add?.has('mtx') || options?.add?.has('multitenancy') || !!env.requires?.multitenancy || false
25
25
  }
26
26
 
27
+ canRun() {
28
+ if (cds.requires.workzone) throw `There is no 'cds add multitenancy' support for SAP BTP WorkZone yet.`
29
+ return true
30
+ }
31
+
27
32
  async run() {
28
33
  const project = readProject()
29
34
  const { isNodejs, isJava, configFile } = project