@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.
- package/CHANGELOG.md +25 -0
- package/bin/cds.js +1 -1
- package/bin/debug.js +122 -26
- package/bin/deploy/index.js +7 -0
- package/bin/import.js +54 -29
- package/bin/repl.js +2 -2
- package/bin/util/fuzzySearch.js +10 -10
- package/bin/watch.js +46 -16
- package/lib/build/buildTaskProviderFactory.js +8 -5
- package/lib/build/plugins/helm/files/subcharts/content-deployment/Chart.yaml +1 -1
- package/lib/build/plugins/helm/files/subcharts/content-deployment/templates/job.yaml +3 -2
- package/lib/build/plugins/helm/files/subcharts/content-deployment/values.yaml +1 -1
- package/lib/build/plugins/helm/files/subcharts/web-application/Chart.yaml +1 -1
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/_helpers.tpl +3 -3
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/api-rule.yaml +1 -1
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/deployment.yaml +13 -5
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/service-binding.yaml +1 -0
- package/lib/build/plugins/helm/files/subcharts/web-application/values.schema.json +28 -15
- package/lib/build/plugins/helm/files/subcharts/web-application/values.yaml +22 -1
- package/lib/import/common.js +174 -44
- package/lib/import/importapi.js +1 -1
- package/lib/import/message.js +1 -1
- package/lib/import/odata/ParserContext.js +46 -0
- package/lib/import/odata/index.js +20 -11
- package/lib/import/odata/v4parser.js +25 -51
- package/lib/init/add.js +0 -2
- package/lib/init/constants.js +3 -3
- package/lib/init/index.js +1 -1
- package/lib/init/merge.js +67 -66
- package/lib/init/mvn.js +16 -12
- package/lib/init/projectReader.js +2 -2
- package/lib/init/template/approuter/files/mta.yaml.hbs +2 -4
- package/lib/init/template/hana/index.js +12 -8
- package/lib/init/template/ias/files/mta.yaml.hbs +15 -0
- package/lib/init/template/ias/index.js +22 -5
- package/lib/init/template/mta/files/mta.yaml.hbs +3 -13
- package/lib/init/template/multitenancy/files/mta.yaml.hbs +1 -0
- package/lib/init/template/multitenancy/index.js +5 -0
- package/lib/init/template/nodejs/files/package.json.hbs +1 -1
- package/lib/init/template/portal/files/portal-site/i18n/catalog.properties +1 -1
- package/lib/init/template/portal/files/portal-site/i18n/catalog_de.properties +1 -1
- package/lib/init/template/portal/files/portal-site/i18n/group.properties +1 -1
- package/lib/init/template/portal/files/portal-site/i18n/group_de.properties +1 -1
- package/lib/init/template/sample/files/typescript/admin-service.ts.hbs +16 -0
- package/lib/init/template/sample/files/typescript/cat-service.ts.hbs +38 -0
- package/lib/init/template/sample/index.js +16 -12
- package/lib/init/template/typescript/files/package.json +1 -1
- package/lib/lint/io.js +6 -16
- package/lib/watch/node-watch.js +3 -3
- package/npm-shrinkwrap.json +49 -33
- package/package.json +1 -1
- /package/lib/init/template/sample/files/{nodejs → javascript}/admin-service.js.hbs +0 -0
- /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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
481
|
-
while (
|
|
482
|
-
|
|
483
|
-
let
|
|
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
|
|
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 =
|
|
644
|
+
associationProperty.target = schemaName + '.' + entityTypeMapping[j];
|
|
665
645
|
}
|
|
666
646
|
}
|
|
667
647
|
} else {
|
|
668
|
-
associationProperty.target =
|
|
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
|
|
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 (
|
|
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
|
package/lib/init/constants.js
CHANGED
|
@@ -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.
|
|
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
|
-
*
|
|
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
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
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 {
|
|
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:
|
|
37
|
-
forwardAuthToken: true
|
|
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
|
|
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' ||
|
|
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
|
-
|
|
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
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
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
|
-
|
|
41
|
-
additions.push(approuter,
|
|
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':
|
|
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}
|
|
57
|
-
srv-cert-url: '${protocol}://${default-host}.cert.${default-domain}'{{/hasIas}}
|
|
47
|
+
srv-url: ${default-url}
|
|
58
48
|
requires: []
|
|
59
49
|
|
|
60
50
|
resources:
|
|
@@ -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
|