@sap/cds-dk 9.4.3 → 9.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 (138) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/bin/compile/index.js +1 -1
  3. package/bin/export.js +1 -1
  4. package/bin/help.js +0 -1
  5. package/bin/import.js +8 -3
  6. package/bin/repl.js +7 -11
  7. package/bin/up.js +150 -20
  8. package/bin/util/containerize.js +66 -25
  9. package/bin/version.js +4 -4
  10. package/lib/bind/k8s.js +1 -1
  11. package/lib/build/plugins/helm/files/templates/_deployment_helpers.tpl +1 -1
  12. package/lib/build/plugins/helm/index.js +1 -1
  13. package/lib/build/provider/hana/2migration.js +16 -24
  14. package/lib/build/provider/hana/index.js +1 -1
  15. package/lib/build/provider/hana/template/.hdiconfig-all +7 -1
  16. package/lib/build/provider/hana/template/package.json +1 -1
  17. package/lib/build/provider/internalBuildPlugin.js +15 -1
  18. package/lib/build/provider/java/index.js +12 -1
  19. package/lib/build/provider/mtx-sidecar/index.js +2 -1
  20. package/lib/build/provider/nodejs/index.js +2 -1
  21. package/lib/build/util.js +3 -3
  22. package/lib/compile/to_openapi.js +1 -5
  23. package/lib/import/common.js +2 -1
  24. package/lib/import/odata/ParserContext.js +1 -0
  25. package/lib/import/odata/v4parser.js +48 -3
  26. package/lib/import/openapi/importOpenAPI.js +6 -1
  27. package/lib/init/constants.js +1 -1
  28. package/lib/init/index.js +15 -6
  29. package/lib/init/merge.js +16 -0
  30. package/lib/init/projectReader.js +19 -4
  31. package/lib/init/registries/mta.js +2 -2
  32. package/lib/init/template/ams/index.js +1 -1
  33. package/lib/init/template/app-frontend/files/app-package.json.hbs +1 -7
  34. package/lib/init/template/app-frontend/files/ui5.yaml.hbs +20 -1
  35. package/lib/init/template/app-frontend/index.js +0 -4
  36. package/lib/init/template/application-logging/index.js +9 -14
  37. package/lib/init/template/approuter/files/mta.yaml.hbs +0 -2
  38. package/lib/init/template/approuter/index.js +6 -7
  39. package/lib/init/template/cloud-logging/index.js +2 -3
  40. package/lib/init/template/containerize/files/containerize.yaml.hbs +17 -2
  41. package/lib/init/template/containerize/index.js +0 -4
  42. package/lib/init/template/data/as-json.js +18 -20
  43. package/lib/init/template/dynatrace/index.js +0 -2
  44. package/lib/init/template/github-actions/files/workflows/cf.yaml.hbs +16 -1
  45. package/lib/init/template/github-actions/files/workflows/test.yaml.hbs +20 -0
  46. package/lib/init/template/github-actions/index.js +5 -8
  47. package/lib/init/template/hana/files/db/package.json +2 -2
  48. package/lib/init/template/hana/files/mta.yaml.hbs +0 -4
  49. package/lib/init/template/hana/index.js +3 -5
  50. package/lib/init/template/helm/index.js +2 -7
  51. package/lib/init/template/helm-unified-runtime/index.js +0 -4
  52. package/lib/init/template/html5-repo/files/app-package.json.hbs +1 -7
  53. package/lib/init/template/html5-repo/files/ui5.yaml.hbs +20 -1
  54. package/lib/init/template/html5-repo/index.js +6 -12
  55. package/lib/init/template/ias/files/values.yaml.hbs +6 -6
  56. package/lib/init/template/ias/index.js +12 -20
  57. package/lib/init/template/kyma/index.js +14 -1
  58. package/lib/init/template/malware-scanner/files/mta.yaml.hbs +3 -1
  59. package/lib/init/template/malware-scanner/files/values.yaml +3 -1
  60. package/lib/init/template/multitenancy/files/mta.yaml.hbs +1 -3
  61. package/lib/init/template/multitenancy/files/values.yaml.hbs +8 -0
  62. package/lib/init/template/multitenancy/index.js +8 -3
  63. package/lib/init/template/nodejs/files/package.json.hbs +1 -1
  64. package/lib/init/template/pipeline/files/app-package.json.hbs +1 -7
  65. package/lib/init/template/pipeline/files/ui5.yaml.hbs +20 -1
  66. package/lib/init/template/pipeline/index.js +1 -2
  67. package/lib/init/template/portal/files/mta.yaml.hbs +5 -3
  68. package/lib/init/template/portal/index.js +3 -6
  69. package/lib/init/template/sample/files/_i18n/i18n_de.properties +12 -0
  70. package/lib/init/template/sample/files/_i18n/i18n_en.properties +21 -0
  71. package/lib/init/template/sample/files/_i18n/i18n_fr.properties +20 -0
  72. package/lib/init/template/sample/files/_i18n/messages_de.properties +34 -0
  73. package/lib/init/template/sample/files/_i18n/messages_en.properties +34 -0
  74. package/lib/init/template/sample/files/_i18n/messages_fr.properties +34 -0
  75. package/lib/init/template/sample/files/app/_i18n/i18n_de.properties +0 -26
  76. package/lib/init/template/sample/files/app/_i18n/i18n_en.properties +8 -0
  77. package/lib/init/template/sample/files/app/admin-authors/fiori-service.cds +52 -0
  78. package/lib/init/template/sample/files/app/admin-authors/webapp/Component.js.hbs +3 -0
  79. package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n.properties +11 -0
  80. package/lib/init/template/sample/files/app/admin-authors/webapp/manifest.json +130 -0
  81. package/lib/init/template/sample/files/app/admin-books/fiori-service.cds +81 -88
  82. package/lib/init/template/sample/files/app/admin-books/webapp/Component.js.hbs +3 -0
  83. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n.properties +10 -2
  84. package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +24 -48
  85. package/lib/init/template/sample/files/app/appconfig/fioriSandboxConfig.json +54 -3
  86. package/lib/init/template/sample/files/app/browse/fiori-service.cds +30 -30
  87. package/lib/init/template/sample/files/app/browse/webapp/Component.js.hbs +3 -0
  88. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n.properties +10 -3
  89. package/lib/init/template/sample/files/app/browse/webapp/manifest.json +18 -23
  90. package/lib/init/template/sample/files/app/common.cds +126 -158
  91. package/lib/init/template/sample/files/app/fiori-apps.html +30 -0
  92. package/lib/init/template/sample/files/app/genres/fiori-service.cds +33 -0
  93. package/lib/init/template/sample/files/app/genres/tree-view.cds +42 -0
  94. package/lib/init/template/sample/files/app/genres/value-help.cds +6 -0
  95. package/lib/init/template/sample/files/app/genres/webapp/Component.js.hbs +3 -0
  96. package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n.properties +4 -0
  97. package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n_de.properties +2 -0
  98. package/lib/init/template/sample/files/app/genres/webapp/manifest.json +126 -0
  99. package/lib/init/template/sample/files/app/services.cds +6 -2
  100. package/lib/init/template/sample/files/db/currencies.cds +13 -0
  101. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Books.csv +5 -5
  102. package/lib/init/template/sample/files/db/data/{sap.capire.bookshop-Books_texts.csv → sap.capire.bookshop-Books.texts.csv} +4 -4
  103. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Genres.csv +42 -15
  104. package/lib/init/template/sample/files/db/data/sap.common-Currencies.csv +12 -0
  105. package/lib/init/template/sample/files/db/data/sap.common-Currencies.texts.csv +15 -0
  106. package/lib/init/template/sample/files/db/hana/index.cds +10 -0
  107. package/lib/init/template/sample/files/db/schema.cds +35 -22
  108. package/lib/init/template/sample/files/db/sqlite/index.cds +10 -0
  109. package/lib/init/template/sample/files/package.json.hbs +14 -1
  110. package/lib/init/template/sample/files/srv/access-control.cds +3 -0
  111. package/lib/init/template/sample/files/srv/admin-service.cds +3 -2
  112. package/lib/init/template/sample/files/srv/cat-service.cds +7 -11
  113. package/lib/init/template/sample/index.js +26 -17
  114. package/lib/init/template/telemetry/index.js +1 -3
  115. package/lib/init/template/typescript/files/package.json +1 -1
  116. package/lib/init/template/workzone-standard/files/mta.yaml.hbs +0 -5
  117. package/lib/init/template/workzone-standard/index.js +0 -1
  118. package/lib/init/template/xsuaa/files/mta.yaml.hbs +4 -0
  119. package/lib/init/template/xsuaa/index.js +3 -5
  120. package/lib/lint/Linter.js +10 -4
  121. package/lib/lint/eslintHelp.json +1 -1
  122. package/lib/mtx/tenant_action.js +12 -4
  123. package/lib/util/checksum.js +32 -0
  124. package/lib/util/question.js +60 -7
  125. package/npm-shrinkwrap.json +59 -35
  126. package/package.json +2 -2
  127. package/lib/init/template/app-frontend/files/ui5-deploy.yaml.hbs +0 -25
  128. package/lib/init/template/github-actions/files/cf-setup/action.yaml +0 -53
  129. package/lib/init/template/html5-repo/files/ui5-deploy.yaml.hbs +0 -25
  130. package/lib/init/template/pipeline/files/ui5-deploy.yaml.hbs +0 -25
  131. package/lib/init/template/sample/files/app/_i18n/i18n.properties +0 -29
  132. package/lib/init/template/sample/files/app/admin-books/webapp/Component.js +0 -8
  133. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n_de.properties +0 -3
  134. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n_en.properties +0 -3
  135. package/lib/init/template/sample/files/app/browse/webapp/Component.js +0 -7
  136. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n_de.properties +0 -3
  137. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n_en.properties +0 -4
  138. package/lib/init/template/sample/files/app/index.html +0 -32
@@ -48,31 +48,23 @@ function _compileToHana(model, options, beforeImage) {
48
48
  let migrations = []
49
49
  let afterImage
50
50
 
51
- if (cds.compile.to.hana) {
52
- const result = cds.compile.to.hana(model, options, beforeImage);
53
-
54
- for (const [content, { file }] of result) {
55
- switch (file) {
56
- case 'deletions.json':
57
- break
58
- case 'migrations.json':
59
- migrations = content
60
- break
61
- case 'afterImage.json':
62
- afterImage = content
63
- break
64
- default: {
65
- const { name, ext: suffix } = path.parse(file)
66
- definitions.push({ name, suffix, sql: content })
67
- }
51
+ const result = cds.compile.to.hana(model, options, beforeImage);
52
+
53
+ for (const [content, { file }] of result) {
54
+ switch (file) {
55
+ case 'deletions.json':
56
+ break
57
+ case 'migrations.json':
58
+ migrations = content
59
+ break
60
+ case 'afterImage.json':
61
+ afterImage = content
62
+ break
63
+ default: {
64
+ const { name, ext: suffix } = path.parse(file)
65
+ definitions.push({ name, suffix, sql: content })
68
66
  }
69
67
  }
70
- } else {
71
- // compatibility with cds < 8.0.0
72
- const result = cdsc.to.hdi.migration(cds.minify(model), options, beforeImage)
73
- definitions = result.definitions
74
- migrations = result.migrations
75
- afterImage = result.afterImage
76
68
  }
77
69
  let migrationTableNames = new Set()
78
70
  if (afterImage) {
@@ -97,7 +89,7 @@ function _compileToHana(model, options, beforeImage) {
97
89
  * @param {String} srcPath Fully qualified path of the directory containing .hdbmigrationtable files
98
90
  * @param {String} migration the migration descriptor
99
91
  * @param {String} tableSql SQL TABLE definition
100
- * @returns {Object} Providing access to 'content' and 'fileName'.
92
+ * @returns {Promise<Object>} Providing access to 'content' and 'fileName'.
101
93
  */
102
94
  async function _2migrationtable(srcPath, migration, tableSql) {
103
95
  let migrationTableModel = null
@@ -586,7 +586,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
586
586
  /**
587
587
  * Returns all hana native artifacts located in db/src/** workspace folders.
588
588
  * @param {Array} workspacePaths Array of workspace paths, 'cds.root' is excluded from the list
589
- * @returns {Array} Array of workspace files, each entry contains the db src directory,
589
+ * @returns {Promise<Array>} Array of workspace files, each entry contains the db src directory,
590
590
  * the relative file path and the file name.
591
591
  */
592
592
  async _getWorkspaceFiles(workspacePaths) {
@@ -167,6 +167,12 @@
167
167
  },
168
168
  "hdbschedulerjob" : {
169
169
  "plugin_name" : "com.sap.hana.di.schedulerjob"
170
+ },
171
+ "hdblogicalschema" : {
172
+ "plugin_name" : "com.sap.hana.di.logicalschema"
173
+ },
174
+ "hdblogicalschemaconfig" : {
175
+ "plugin_name" : "com.sap.hana.di.logicalschema.config"
170
176
  }
171
177
  }
172
- }
178
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "deploy",
3
3
  "dependencies": {
4
- "hdb": "^0",
4
+ "hdb": "^2.0.0",
5
5
  "@sap/hdi-deploy": "^5"
6
6
  },
7
7
  "engines": {
@@ -4,6 +4,7 @@ const cds = require('../../cds')
4
4
  const Plugin = require('../plugin')
5
5
  const { hasOptionValue, getFiles } = require('../util')
6
6
  const { FOLDER_GEN, DEFAULT_BUILTIN_PRIORITY_MAX } = require('../constants')
7
+ const TRACE = cds.debug('trace')
7
8
 
8
9
  class InternalBuildPlugin extends Plugin {
9
10
  /**
@@ -116,7 +117,7 @@ class InternalBuildPlugin extends Plugin {
116
117
  cwd: cds.root,
117
118
  src: this.hasBuildOption('flatDeployLayout', true) || this.task.src === this.task.dest ? this.task.src : cds.root
118
119
  }
119
- const csnStr = cds.compile.to.json(model, jsonOptions)
120
+ const csnStr = cds_compile_to_json(model, jsonOptions)
120
121
  await this.write(csnStr).to(csnFile)
121
122
  return csnStr
122
123
  }
@@ -156,4 +157,17 @@ class InternalBuildPlugin extends Plugin {
156
157
  return null
157
158
  }
158
159
  }
160
+
161
+ // Emits the 'compile.for.runtime' event, as the produced CSN is for runtime usage.
162
+ // Plugins use it to modify the CSN before it is stringified.
163
+ function cds_compile_to_json (csn,o) {
164
+ TRACE?.time('cds.compile 2json'.padEnd(22)); try {
165
+ let result, next = ()=> result ??= cds.compile.to.json (csn,o)
166
+ cds.emit ('compile.for.runtime', csn, o, next)
167
+ return next() //> in case no handler called next
168
+ }
169
+ finally { TRACE?.timeEnd('cds.compile 2json'.padEnd(22)) }
170
+ }
171
+
172
+
159
173
  module.exports = InternalBuildPlugin
@@ -7,6 +7,7 @@ const { OUTPUT_MODE, OUTPUT_MODE_RESULT, FILE_EXT_CDS, CONTENT_LANGUAGE_BUNDLES,
7
7
  const { getI18nDefaultFolder } = require('../../util')
8
8
  const DEFAULT_COMPILE_DEST_FOLDER = path.normalize('src/main/resources/edmx')
9
9
  const DEBUG = cds.debug('cli|build')
10
+ const LOG = cds.log('cli|build')
10
11
 
11
12
  class JavaBuildPlugin extends EdmxBuildPlugin {
12
13
  init() {
@@ -25,7 +26,17 @@ class JavaBuildPlugin extends EdmxBuildPlugin {
25
26
 
26
27
  if (!this.hasBuildOption(CONTENT_EDMX, false)) {
27
28
  // generate edmx files containing all features - localized edmx for backward compatibility
28
- if (this.hasBuildOption(CONTENT_LOCALIZED_EDMX, true)) {
29
+ let localized = this.hasBuildOption(CONTENT_LOCALIZED_EDMX, true)
30
+
31
+ // Force localized to true for OData v2
32
+ if (cds.env.odata.version === 'v2') {
33
+ if (!localized) {
34
+ LOG.info('Forcing localized EDMX generation for OData v2')
35
+ localized = true
36
+ }
37
+ }
38
+
39
+ if (localized) {
29
40
  await this.compileToEdmx(model, compileDest, { [FLAVOR_LOCALIZED_EDMX]: true })
30
41
  }
31
42
  // non-localized edmx
@@ -86,7 +86,8 @@ class MtxSidecarBuildPlugin extends NodejsBuildPlugin {
86
86
  return
87
87
  }
88
88
  const { dictionary, sources } = await this.compileAll(model, destMainSrv, destMain)
89
- await this.collectAllLanguageBundles(dictionary, sources, destMainSrv, destMain)
89
+ // await this.collectAllLanguageBundles(dictionary, sources, destMainSrv, destMain)
90
+ await this.collectAllLanguageBundles(dictionary, sources, destMain, destMain)
90
91
 
91
92
  if (!this.hasBuildOption(CONTENT_EDMX, false)) {
92
93
  //inferred model expected by cds.compile.to.edmx()
@@ -53,7 +53,8 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
53
53
  if (model) {
54
54
  const { dictionary, sources } = await this.compileAll(model, destSrv, destRoot)
55
55
  // collect and write language bundles into single i18n.json file
56
- await this.collectAllLanguageBundles(dictionary, sources, destSrv, destRoot)
56
+ await this.collectAllLanguageBundles(dictionary, sources, destRoot, destRoot)
57
+ // await this.collectAllLanguageBundles(dictionary, sources, destSrv, destRoot)
57
58
 
58
59
  if (!this.hasBuildOption(CONTENT_EDMX, false)) {
59
60
  const compileOptions = { [FLAVOR_LOCALIZED_EDMX]: this.hasBuildOption(FLAVOR_LOCALIZED_EDMX, true) }
package/lib/build/util.js CHANGED
@@ -165,9 +165,9 @@ function flatten(modelPaths) {
165
165
 
166
166
  function getI18nDefaultFolder() {
167
167
  // keep '_i18n' as preferred folder for compatibility reasons
168
- if (cds.env.i18n.folders.includes('_i18n')) return '_i18n'
169
- if (cds.env.i18n.folders.includes('i18n')) return 'i18n'
170
- return cds.env.i18n.folders[0] ?? 'i18n'
168
+ // if (cds.env.i18n.folders.includes('_i18n')) return '_i18n'
169
+ // if (cds.env.i18n.folders.includes('i18n')) return 'i18n'
170
+ return cds.env.i18n.folders?.[0] ?? '_i18n'
171
171
  }
172
172
 
173
173
  async function pathExists(path) {
@@ -1,8 +1,4 @@
1
1
  const cds = require('../cds')
2
- const { compile } = cds._local('@cap-js/openapi') // load a local cap-js/openapi (fallback to our own)
3
-
4
- function cds_compile_to_openapi (csn, options) {
5
- return compile(csn, options)
6
- }
2
+ const { compile: cds_compile_to_openapi } = cds._local('@cap-js/openapi') // load a local cap-js/openapi (fallback to our own)
7
3
 
8
4
  module.exports = cds_compile_to_openapi
@@ -510,7 +510,8 @@ async function postProcess(filePath, options, csn, cwd) {
510
510
  usingStatements.push(`using from './${path.basename(csnFile.replace(`'`, `''`))}';`);
511
511
  }
512
512
 
513
- _printUsageHint(csnFile, services, cwd);
513
+ if (!options.dry)
514
+ _printUsageHint(csnFile, services, cwd);
514
515
 
515
516
  if (!options.dry && !options.no_copy)
516
517
  await _copyToSrvExternal(filePath, destFilePath, cwd);
@@ -38,6 +38,7 @@ module.exports = class ParserContext {
38
38
  this.schemaAliasToNamespace = {};
39
39
  this.schemaToSchemaDataIndex = {};
40
40
  this.schemaToPrimarySchema = {};
41
+ this.schemaDetails = {};
41
42
  this.vocabNamespaceToAlias = {};
42
43
  this.entityToBoundActions = {};
43
44
  this.entityToBoundFunctions = {};
@@ -634,7 +634,7 @@ function _generateStructuredDataTypeCsn(dataType, propertyObject, isPrimaryKey,
634
634
 
635
635
  function _parseAssociationProperty(foreignReference, associationNode, parserContext) {
636
636
  const associationProperty = {};
637
- associationProperty.type = (associationNode.$OnDelete === 'Cascade') ? 'cds.Composition' : 'cds.Association';
637
+ associationProperty.type = (associationNode.$OnDelete === 'Cascade' || associationNode.$ContainsTarget) ? 'cds.Composition' : 'cds.Association';
638
638
 
639
639
  let schemaName = associationNode.$Type.substring(0, associationNode.$Type.lastIndexOf('.'));
640
640
 
@@ -642,6 +642,11 @@ function _parseAssociationProperty(foreignReference, associationNode, parserCont
642
642
  schemaName = parserContext.schemaAliasToNamespace[schemaName];
643
643
  }
644
644
 
645
+ if (associationNode.$ContainsTarget) {
646
+ parserContext.schemaDetails[schemaName] ??= {}
647
+ parserContext.schemaDetails[schemaName].containment = true
648
+ }
649
+
645
650
  let resolvedEntityName;
646
651
  let entityTypeMapping = (
647
652
  parserContext.entityToEntitySetMap?.get(associationNode.$Type) ||
@@ -676,9 +681,18 @@ function _parseAssociationProperty(foreignReference, associationNode, parserCont
676
681
  } else {
677
682
  associationProperty.cardinality.max = 1;
678
683
  }
679
- // Convert managed associations and compositions in unmanaged with empty key to avoid
684
+
685
+ // Generate on condition in case $Partner exists, as that indicates a backlink with which the
686
+ // condition can be generated.
687
+ // If not given convert managed associations and compositions in unmanaged with empty key to avoid
680
688
  // "generation" of keys, that do not exist in the external service.
681
- if (common.checkForEmptyKeys(associationProperty, "V4")) {
689
+ if (associationNode.$Partner && associationProperty.type === 'cds.Composition') {
690
+ associationProperty.on = [
691
+ {ref: [foreignReference, associationNode.$Partner]},
692
+ '=',
693
+ {ref: ['$self']}
694
+ ]
695
+ } else if (common.checkForEmptyKeys(associationProperty, "V4")) {
682
696
  associationProperty.keys = [];
683
697
  }
684
698
  _captureAnnotationWithinElement(associationNode, associationProperty, parserContext);
@@ -1653,12 +1667,43 @@ function _generateCSN(edmxAsJson, ignorePersistenceSkip, parserContext, importer
1653
1667
  csn.definitions[parserContext.primarySchema].kind = 'service';
1654
1668
  csn.definitions[parserContext.primarySchema]['@cds.external'] = true;
1655
1669
 
1670
+ if (parserContext.schemaDetails[parserContext.primarySchema]?.containment) {
1671
+ csn.definitions[parserContext.primarySchema]['@odata.containment'] = true;
1672
+ }
1673
+
1656
1674
  // Step 8: parse entity container contents and their annotations
1657
1675
  let schemaData = schemaDataList[parserContext.schemaToSchemaDataIndex[parserContext.primarySchema]];
1658
1676
  let entityContainerElements = schemaData[parserContext.entityContainerName];
1659
1677
  csn.definitions = Object.assign(_parseEntityContainer(csn.definitions, entityContainerElements, parserContext), csn.definitions);
1660
1678
  }
1661
1679
 
1680
+
1681
+ // Step 9: 2. Step for generating $ContainsTarget & $Partner links
1682
+ // The containing composition is already generated. Now the Keys: [] has to be removed
1683
+ // on the backlink association because per OData spec, OData does not expose the foreign key fields
1684
+ // used by the backlink and thus it is save to assume they must be generated for proper mocking
1685
+ //
1686
+ // This happens after Step 8 because that adjusts the names of entity container entities where
1687
+ // entity type and entity set differ in naming
1688
+ for (const entity in csn.definitions) {
1689
+ if (entity.type !== 'entity') continue;
1690
+
1691
+ const containingCompositions = Object.keys(csn.definitions[entity].elements).filter(e =>
1692
+ csn.definitions[entity].elements[e].type === 'cds.Composition' &&
1693
+ csn.definitions[entity].elements[e].on
1694
+ );
1695
+ for (const comp of containingCompositions) {
1696
+ const compTarget = csn.definitions[csn.definitions[entity].elements[comp].target];
1697
+ const backLink = Object.keys(compTarget.elements).find(e =>
1698
+ compTarget.elements[e].type === 'cds.Association' &&
1699
+ compTarget.elements[e].target === entity
1700
+ )
1701
+ if (compTarget.elements[backLink].keys) {
1702
+ delete compTarget.elements[backLink].keys
1703
+ }
1704
+ }
1705
+ }
1706
+
1662
1707
  return JSON.stringify(csn, null, 4);
1663
1708
  }
1664
1709
 
@@ -4,6 +4,8 @@ const {
4
4
  pathAndMethod,
5
5
  serviceName,
6
6
  } = require("./utilities");
7
+ const cds = require('../../cds');
8
+
7
9
 
8
10
  module.exports = { importOpenAPI };
9
11
 
@@ -32,7 +34,10 @@ function importOpenAPI(input) {
32
34
  };
33
35
 
34
36
  const context = {
35
- serviceName: serviceName(input.info?.title || "TODO.service"),
37
+ serviceName: serviceName(cds.cli.options["into-namespace"]
38
+ ?? input.info?.title
39
+ ?? "TODO.service"
40
+ ),
36
41
  oasVersion: input.openapi || input.swagger,
37
42
  v3: !!input.openapi,
38
43
  schemas: input.openapi ? input.components?.schemas : input.definitions,
@@ -5,7 +5,7 @@ module.exports = {
5
5
  COMMAND_ADD: 'add',
6
6
 
7
7
  JAVA_LTS_VERSIONS: [17, 21],
8
- MAVEN_ARCHETYPE_VERSION: '4.4.1',
8
+ MAVEN_ARCHETYPE_VERSION: '4.4.2',
9
9
 
10
10
  OPTIONS: Object.freeze({
11
11
  COMPLETION: 'completion',
package/lib/init/index.js CHANGED
@@ -231,17 +231,26 @@ module.exports = class CDSGenerator {
231
231
  const templates = new Map
232
232
  for (let plugin of plugins) {
233
233
  // Compat
234
- if (plugin === 'appfront') plugin = 'app-front'
235
234
  if (plugin === 'helm' && cds.cli.options['internal-unified-runtime-charts']) {
235
+ console.warn(term.warn('"--internal-unified-runtime-charts" is deprecated, please use "cds add kyma --unified-runtime" instead'))
236
236
  plugin = 'helm-unified-runtime'
237
237
  cds.cli.options.add?.delete('helm')
238
238
  cds.cli.options.add?.add('helm-unified-runtime')
239
239
  }
240
- if (plugin === 'sample-tiny') plugin = 'tiny-sample'
241
- if (plugin in { 'kibana-logging': 1, 'kibana': 1 }) plugin = 'application-logging'
242
- if (plugin === 'postgresql') plugin = 'postgres'
243
- if (plugin === 'gha') plugin = 'github-actions'
244
- if (plugin === 'app-front') plugin = 'app-frontend'
240
+ const _alias = (alias, preferred) => {
241
+ if (plugin === alias) {
242
+ plugin = preferred
243
+ cds.cli.options.add?.delete(alias)
244
+ cds.cli.options.add?.add(preferred)
245
+ cds.cli.argv[0] = cds.cli.argv[0].replace(alias, preferred)
246
+ }
247
+ }
248
+ _alias('appfront', 'app-frontend')
249
+ _alias('app-front', 'app-frontend')
250
+ _alias('gha', 'github-actions')
251
+ _alias('postgresql', 'postgres')
252
+ _alias('kibana', 'application-logging')
253
+ _alias('kibana-logging', 'application-logging')
245
254
 
246
255
  if (!templates.has(plugin)) {
247
256
  const template = await this._createTemplate(plugin)
package/lib/init/merge.js CHANGED
@@ -106,6 +106,9 @@ async function _mergeYAML(into, from, project, semantics = {}) {
106
106
  if (project) await writeYAML(into, source)
107
107
  else await copy(from, into)
108
108
  return source
109
+ } else if (typeof into === 'string') {
110
+ await writeYAML(into, source)
111
+ return source
109
112
  }
110
113
  throw new Error(`Target YAML doesn't exist`)
111
114
  }
@@ -113,6 +116,19 @@ async function _mergeYAML(into, from, project, semantics = {}) {
113
116
  const entryMap = new Map, templateEntryMap = new Map
114
117
 
115
118
  const { additions, overwrites, deletions, relationships } = semantics
119
+ if (relationships && Array.isArray(relationships)) {
120
+ semantics.additions ??= []
121
+ const add = semantics.additions
122
+
123
+ for (const rel of relationships) {
124
+ for (const ins of (Array.isArray(rel.insert) ? rel.insert : [rel.insert])) {
125
+ if (!add.includes(ins)) add.push(ins)
126
+ }
127
+ const base = rel.into?.[0]
128
+ if (base && !add.includes(base)) add.push(base)
129
+ }
130
+ }
131
+
116
132
  for (const entry of additions ?? []) {
117
133
  entryMap.set(entry, undefined)
118
134
  templateEntryMap.set(entry, undefined)
@@ -123,9 +123,9 @@ module.exports = new class ProjectReader {
123
123
  const inbounds = manifest?.['sap.app']?.crossNavigation?.inbounds
124
124
  const [firstInbound] = Object.values(inbounds ?? { intent: { semanticObject: 'Books', action: 'display' } })
125
125
  const vizId = firstInbound.semanticObject + '-' + firstInbound.action
126
- // Try to read archiveName from ui5-deploy.yaml if it exists
126
+ // Try to read archiveName from ui5.yaml if it exists
127
127
  let archiveName = ''
128
- const ui5DeployPath = path.resolve(cds.root, cds.env.folders.app, app, 'ui5-deploy.yaml')
128
+ const ui5DeployPath = path.resolve(cds.root, cds.env.folders.app, app, 'ui5.yaml')
129
129
  if (exists(ui5DeployPath)) {
130
130
  const ui5Deploy = cds.load.yaml(ui5DeployPath)
131
131
  const customTasks = ui5Deploy?.builder?.customTasks
@@ -159,6 +159,7 @@ module.exports = new class ProjectReader {
159
159
  hasUIEmbedded: _uiEmbedded,
160
160
  hasUIModule: _uiModule,
161
161
  hasUI: () => _uiEmbedded() || _uiModule(),
162
+ hasUI5: () => _uiEmbedded() || _uiModule(),
162
163
  literal: () => () => text => text,
163
164
  hasMTXRoute: () => _inProd('extensibility') || (_inProd('multitenancy') && !_isJava()),
164
165
  hasMTXRouteJava: () => (_inProd('helm-unified-runtime') || _inProd('helm')) && _isJava() && _inProd('multitenancy'),
@@ -175,7 +176,21 @@ module.exports = new class ProjectReader {
175
176
  return this.npmWorkspaceRoot() !== cds.root
176
177
  },
177
178
  hasRoles: () => { }, roles: () => { }, // for xs-security generation
178
- hasCustomRoleCollections: () => exists('xs-security.json') && !!JSON.parse(fs.readFileSync(path.join(cds.root, 'xs-security.json')))['role-collections']
179
+ hasCustomRoleCollections: () => exists('xs-security.json') && !!JSON.parse(fs.readFileSync(path.join(cds.root, 'xs-security.json')))['role-collections'],
180
+ k8s: () => {
181
+ if (!exists('chart/values.yaml')) return {}
182
+
183
+ const values = cds.load.yaml(join(cds.root, 'chart/values.yaml'))
184
+
185
+ let containerize = {}
186
+ if (exists('containerize.yaml'))
187
+ containerize = cds.load.yaml(join(cds.root, 'containerize.yaml'))
188
+
189
+ return {
190
+ values: values ?? {},
191
+ containerize: containerize ?? {}
192
+ }
193
+ }
179
194
  }
180
195
 
181
196
  // Automatically creates availability checks for `cds add` commands.
@@ -311,4 +326,4 @@ function _normalizeSpringBootCfg(obj) {
311
326
  * @property {String} githubSlug GitHub org/repo.
312
327
  * @property {boolean} isMonorepoMicroservice Indicates if the project is a monorepo microservice.
313
328
  * @property {String} npmWorkspaceRoot The root path of the monorepo workspace.
314
- */
329
+ */
@@ -152,9 +152,9 @@ const requiredAppApi4 = module => ({
152
152
  where: { name: 'app-api' }
153
153
  })
154
154
 
155
- const requiredMtxSidecarApi4 = srv => ({
155
+ const requiredMtxSidecarApi4 = (srv, name) => ({
156
156
  in: [srv, 'requires'],
157
- where: { 'properties.CDS_MULTITENANCY_SIDECAR_URL': '~{mtx-url}' }
157
+ where: { 'properties.CDS_MULTITENANCY_SIDECAR_URL': name }
158
158
  })
159
159
 
160
160
  const requiredJavaApprouterApi4 = srv => ({
@@ -15,7 +15,7 @@ module.exports = class AmsTemplate extends require('../../plugin') {
15
15
 
16
16
  static hasInProduction(env) {
17
17
  const { dependencies, devDependencies } = readProject()
18
- return env.requires?.auth?.ams || devDependencies?.['@sap/ams-dev'] || dependencies?.['@sap/ams']
18
+ return env.requires?.auth?.ams || devDependencies?.['@sap/ams-dev'] || devDependencies?.['@sap/ams'] || dependencies?.['@sap/ams']
19
19
  }
20
20
 
21
21
  async run() {
@@ -3,18 +3,12 @@
3
3
  "version": "1.0.0",
4
4
  "main": "webapp/index.html",
5
5
  "scripts": {
6
- "build": "ui5 build preload --clean-dest --config ui5-deploy.yaml",
7
- "build-local": "ui5 build preload --clean-dest",
6
+ "build": "ui5 build preload --clean-dest",
8
7
  "start": "ui5 serve"
9
8
  },
10
9
  "devDependencies": {
11
10
  "@ui5/cli": "^4",
12
11
  "ui5-task-zipper": "^3"
13
- },
14
- "ui5": {
15
- "dependencies": [
16
- "ui5-task-zipper"
17
- ]
18
12
  }
19
13
  {{/hasUI5}}
20
14
  }
@@ -1,6 +1,25 @@
1
1
  ---
2
2
  # yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json
3
- specVersion: '3.1'
3
+ specVersion: '4.0'
4
4
  metadata:
5
5
  name: {{app}}
6
6
  type: application
7
+ resources:
8
+ configuration:
9
+ propertiesFileSourceEncoding: UTF-8
10
+ builder:
11
+ resources:
12
+ excludes:
13
+ - "/test/**"
14
+ - "/localService/**"
15
+ customTasks:
16
+ - name: ui5-task-zipper
17
+ afterTask: generateVersionInfo
18
+ configuration:
19
+ archiveName: {{app}}
20
+ additionalFiles:
21
+ - xs-app.json
22
+ {{#isTypescript}}
23
+ - name: ui5-tooling-transpile-task
24
+ afterTask: replaceVersion
25
+ {{/isTypescript}}
@@ -26,10 +26,6 @@ module.exports = class extends require('../../plugin') {
26
26
  project.app = app
27
27
  await merge(__dirname, 'files/ui5.yaml.hbs').into(join(appPath, app, 'ui5.yaml'), { project })
28
28
  }
29
- for (const { app } of apps) {
30
- project.app = app
31
- await merge(__dirname, 'files/ui5-deploy.yaml.hbs').into(join(appPath, app, 'ui5-deploy.yaml'), { project })
32
- }
33
29
  }
34
30
 
35
31
  for (const { app } of apps) {
@@ -28,28 +28,23 @@ module.exports = class ApplicationLoggingTemplate extends require('../../plugin'
28
28
 
29
29
  async combine() {
30
30
  const project = readProject()
31
- const { addMta, addApprouter, isJava, addMultitenancy, srvPath } = project
31
+ const { addMta, hasMta, hasApprouter, isJava, addMultitenancy, hasMultitenancy, srvPath } = project
32
32
 
33
- if (addMta || addMultitenancy) {
33
+ if (addMta || (addMultitenancy && hasMta)) {
34
34
  const additions = [], relationships = []
35
- if (addMta) {
36
- const srv = srv4(srvPath)
37
- additions.push(srv, applicationLogging)
38
- relationships.push({
39
- insert: [applicationLogging, 'name'],
40
- into: [srv, 'requires', 'name']
41
- })
42
- }
43
- if (addMultitenancy) {
35
+ const srv = srv4(srvPath)
36
+ relationships.push({
37
+ insert: [applicationLogging, 'name'],
38
+ into: [srv, 'requires', 'name']
39
+ })
40
+ if (hasMultitenancy) {
44
41
  const mtxSidecar = mtxSidecar4(isJava ? 'mtx/sidecar' : 'gen/mtx/sidecar')
45
- additions.push(mtxSidecar)
46
42
  relationships.push({
47
43
  insert: [applicationLogging, 'name'],
48
44
  into: [mtxSidecar, 'requires', 'name']
49
45
  })
50
46
  }
51
- if (addApprouter) {
52
- additions.push(approuter)
47
+ if (hasApprouter) {
53
48
  relationships.push({
54
49
  insert: [applicationLogging, 'name'],
55
50
  into: [approuter, 'requires', 'name']
@@ -57,7 +57,6 @@ modules:
57
57
  url: ${default-url}
58
58
  {{/isJava}}
59
59
 
60
- {{#hasMultitenancy}}
61
60
  - name: {{appName}}-mtx
62
61
  type: nodejs
63
62
  path: {{#isNodejs}}gen/{{/isNodejs}}mtx/sidecar{{#isNodejs}}
@@ -66,7 +65,6 @@ modules:
66
65
  properties:
67
66
  SUBSCRIPTION_URL: ~{app-protocol}://\${tenant_subdomain}-~{app-uri}
68
67
  {{/isNodejs}}
69
- {{/hasMultitenancy}}
70
68
 
71
69
  resources:
72
70
  - name: {{appName}}-auth
@@ -35,30 +35,29 @@ module.exports = class ApprouterTemplate extends require('../../plugin') {
35
35
  const {
36
36
  isNodejs, isJava,
37
37
  addIas, addXsuaa, hasXsuaa, hasIas, addMultitenancy, hasMTXRoute, hasMTXRouteJava,
38
- addMta, addHelm, addHelmUnifiedRuntime, hasMta, hasHelm, hasHelmUnifiedRuntime,
38
+ addMta, addHelm, addHelmUnifiedRuntime,
39
+ hasMta, hasHelm, hasHelmUnifiedRuntime, hasMultitenancy,
39
40
  addContainerize, srvPath, approuterPath
40
41
  } = project
41
42
 
42
43
 
43
44
  if (addMta || (addMultitenancy || addXsuaa || addIas) && hasMta) {
44
45
  const srv = srv4(srvPath)
45
- const apis = isJava && addMultitenancy ? [requiredJavaApprouterApi4(srv), providedJavaApprouterApi] : []
46
+ const apis = isJava && hasMultitenancy ? [requiredJavaApprouterApi4(srv), providedJavaApprouterApi] : []
46
47
  const mtxSidecar = mtxSidecar4(isJava ? 'mtx/sidecar' : 'gen/mtx/sidecar')
47
- if (isNodejs && addMultitenancy) apis.push(requiredMtxApi, requiredAppApi4(mtxSidecar), providedAppApi)
48
- if (isJava && addMultitenancy) apis.push(requiredMtxApi)
48
+ if (isNodejs && hasMultitenancy) apis.push(requiredMtxApi, requiredAppApi4(mtxSidecar), providedAppApi)
49
+ if (isJava && hasMultitenancy) apis.push(requiredMtxApi)
49
50
  const modules = [srv, approuter]
50
- if (addMultitenancy) modules.push(mtxSidecar)
51
+ if (hasMultitenancy) modules.push(mtxSidecar)
51
52
  const additions = [...modules, ...apis]
52
53
  const relationships = []
53
54
  if (hasXsuaa) {
54
- additions.push(xsuaa)
55
55
  relationships.push({
56
56
  insert: [xsuaa, 'name'],
57
57
  into: [approuter, 'requires', 'name']
58
58
  })
59
59
  }
60
60
  if (hasIas) {
61
- additions.push(ias)
62
61
  relationships.push({
63
62
  insert: [ias, 'name'],
64
63
  into: [approuter, 'requires', 'name']