@sap/cds-dk 7.4.0 → 7.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 (179) hide show
  1. package/bin/activate.js +1 -1
  2. package/bin/add.js +6 -4
  3. package/bin/build.js +10 -12
  4. package/bin/cds.js +4 -1
  5. package/bin/deploy/index.js +5 -2
  6. package/bin/eval.js +1 -1
  7. package/bin/extend.js +3 -3
  8. package/bin/login.js +2 -2
  9. package/bin/logout.js +1 -1
  10. package/bin/pull.js +1 -1
  11. package/bin/push.js +5 -5
  12. package/bin/subscribe.js +2 -2
  13. package/bin/unsubscribe.js +2 -2
  14. package/bin/upgrade.js +2 -2
  15. package/bin/version.js +2 -2
  16. package/bin/watch.js +1 -1
  17. package/lib/bind/bindingUtil.js +6 -3
  18. package/lib/bind/cfKeyProvider.js +1 -1
  19. package/lib/bind/userCdsrcJsonStorageProvider.js +1 -1
  20. package/lib/build/buildPlugin.js +6 -12
  21. package/lib/build/buildTaskEngine.js +24 -19
  22. package/lib/build/buildTaskFactory.js +27 -33
  23. package/lib/build/buildTaskProvider.js +1 -1
  24. package/lib/build/buildTaskProviderFactory.js +26 -41
  25. package/lib/build/constants.js +0 -1
  26. package/lib/build/index.js +3 -3
  27. package/lib/build/plugins.js +15 -15
  28. package/lib/build/provider/edmxBuildPlugin.js +6 -7
  29. package/lib/build/provider/fiori/index.js +8 -8
  30. package/lib/build/provider/hana/2migration.js +12 -16
  31. package/lib/build/provider/hana/index.js +5 -7
  32. package/lib/build/provider/hana/template/package.json +1 -1
  33. package/lib/build/provider/internalBuildPlugin.js +0 -8
  34. package/lib/build/provider/internalBuildTaskProvider.js +17 -21
  35. package/lib/build/provider/java/index.js +2 -1
  36. package/lib/build/provider/mtx-extension/index.js +8 -5
  37. package/lib/build/provider/mtx-sidecar/index.js +2 -1
  38. package/lib/build/provider/nodejs/index.js +64 -58
  39. package/lib/compile/openapi/csdl2openapi.js +29 -15
  40. package/lib/compile/openapi/index.js +6 -6
  41. package/lib/deploy/to-hana/hana.js +5 -3
  42. package/lib/env/schema.js +2 -3
  43. package/lib/index.js +68 -28
  44. package/lib/init/add.js +17 -0
  45. package/lib/init/constants.js +5 -3
  46. package/lib/init/index.js +39 -25
  47. package/lib/init/{util/merge.js → merge.js} +32 -20
  48. package/lib/init/{util/mvn.js → mvn.js} +5 -5
  49. package/lib/init/plugin.js +85 -0
  50. package/lib/init/projectReader.js +145 -0
  51. package/lib/init/registries/mta.js +151 -0
  52. package/lib/init/template/{kibana → application-logging}/files/mta.yaml.hbs +3 -3
  53. package/lib/init/template/application-logging/index.js +49 -0
  54. package/lib/init/template/approuter/files/mta.yaml.hbs +11 -11
  55. package/lib/init/template/approuter/files/xs-app.json.hbs +2 -2
  56. package/lib/init/template/approuter/index.js +36 -81
  57. package/lib/init/template/cf-manifest/files/manifest.yml.hbs +3 -3
  58. package/lib/init/template/cf-manifest/files/services-manifest.yml.hbs +4 -4
  59. package/lib/init/template/cf-manifest/index.js +4 -4
  60. package/lib/init/template/connectivity/index.js +22 -35
  61. package/lib/init/template/data/index.js +3 -2
  62. package/lib/init/template/destination/files/values.yaml +9 -0
  63. package/lib/init/template/destination/index.js +15 -47
  64. package/lib/init/template/enterprise-messaging/index.js +26 -58
  65. package/lib/init/template/enterprise-messaging-shared/index.js +23 -39
  66. package/lib/init/template/extensibility/index.js +16 -30
  67. package/lib/init/template/file-based-messaging/index.js +7 -8
  68. package/lib/init/template/h2/index.js +4 -4
  69. package/lib/init/template/hana/files/mta.yaml.hbs +8 -8
  70. package/lib/init/template/hana/index.js +29 -115
  71. package/lib/init/template/helm/files/chart/templates/NOTES.txt +12 -0
  72. package/lib/init/template/helm/files/chart/templates/_deployment_helpers.tpl +82 -0
  73. package/lib/init/template/helm/files/chart/templates/_helpers.tpl +28 -0
  74. package/lib/init/template/helm/files/chart/values.root.schema.json +61 -0
  75. package/lib/init/template/helm/files/chart/values.schema.json +366 -0
  76. package/lib/init/template/helm/{subcharts → files}/web-application/Chart.yaml +1 -1
  77. package/lib/init/template/helm/{subcharts → files}/web-application/templates/_api.tpl +1 -1
  78. package/lib/init/template/helm/{subcharts → files}/web-application/values.yaml +1 -1
  79. package/lib/init/template/helm/index.js +84 -85
  80. package/lib/init/template/helm-unified-runtime/files/Chart.yaml.hbs +10 -0
  81. package/lib/init/template/helm-unified-runtime/files/values.yaml.hbs +26 -0
  82. package/lib/init/template/helm-unified-runtime/index.js +91 -0
  83. package/lib/init/template/helm-unified-runtime/lib/build.js +0 -0
  84. package/lib/init/template/helm-unified-runtime/package.json +4 -0
  85. package/lib/init/template/html5-repo/files/values.yaml.hbs +28 -0
  86. package/lib/init/template/html5-repo/index.js +23 -97
  87. package/lib/init/template/java/index.js +3 -3
  88. package/lib/init/template/lint/index.js +2 -2
  89. package/lib/init/template/liquibase/index.js +16 -0
  90. package/lib/init/template/local-messaging/index.js +7 -8
  91. package/lib/init/template/mta/index.js +7 -19
  92. package/lib/init/template/mtx/index.js +2 -2
  93. package/lib/init/template/multitenancy/index.js +38 -89
  94. package/lib/init/template/nodejs/index.js +5 -4
  95. package/lib/init/template/notebook/index.js +1 -1
  96. package/lib/init/template/notifications/index.js +16 -19
  97. package/lib/init/template/pipeline/index.js +1 -1
  98. package/lib/init/template/postgres/index.js +25 -31
  99. package/lib/init/template/redis-messaging/index.js +23 -27
  100. package/lib/init/template/sample/index.js +4 -4
  101. package/lib/init/template/samples/index.js +2 -2
  102. package/lib/init/template/sqlite/files/{package.json.hbs → package.json} +0 -2
  103. package/lib/init/template/sqlite/index.js +6 -10
  104. package/lib/init/template/tiny-sample/index.js +6 -6
  105. package/lib/init/template/toggles/index.js +7 -8
  106. package/lib/init/template/typer/index.js +6 -6
  107. package/lib/init/template/xsuaa/files/mta.yaml.hbs +3 -3
  108. package/lib/init/template/xsuaa/files/values.yaml.hbs +3 -3
  109. package/lib/init/template/xsuaa/index.js +27 -52
  110. package/lib/init/{util/template.js → template.js} +1 -1
  111. package/lib/{client → mtx}/activate.js +15 -11
  112. package/lib/{client → mtx}/auth_manager.js +5 -4
  113. package/lib/{client → mtx}/download_legacy.js +6 -4
  114. package/lib/{client → mtx}/extend.js +14 -9
  115. package/lib/{client → mtx}/index.js +1 -1
  116. package/lib/{client → mtx}/params.js +4 -1
  117. package/lib/{client → mtx}/pull.js +6 -4
  118. package/lib/{client → mtx}/push.js +11 -10
  119. package/lib/{client → mtx}/settings_manager.js +8 -6
  120. package/lib/{client → mtx}/tenant_action.js +9 -8
  121. package/lib/{client/helper/cf_helper.js → mtx/util/cf.js} +4 -3
  122. package/lib/{client/helper/dependency_helper.js → mtx/util/dependencies.js} +1 -0
  123. package/lib/mtx/util/errors.js +27 -0
  124. package/lib/mtx/util/legacy_ext.js +16 -0
  125. package/lib/{client/helper/url_helper.js → mtx/util/urls.js} +12 -1
  126. package/lib/util/fs.js +5 -1
  127. package/lib/util/pruneAxiosErrors.js +35 -18
  128. package/lib/util/write.js +1 -1
  129. package/npm-shrinkwrap.json +27 -27
  130. package/package.json +10 -1
  131. package/lib/client/base_command.js +0 -54
  132. package/lib/init/template/_merging/registry-mta.js +0 -304
  133. package/lib/init/template/helm/Model.d.ts +0 -53
  134. package/lib/init/template/kibana/files/package.json.hbs +0 -17
  135. package/lib/init/template/kibana/index.js +0 -54
  136. package/lib/init/template/templateBase.js +0 -100
  137. package/lib/init/util/md5.js +0 -86
  138. package/lib/init/util/projectReader.js +0 -94
  139. /package/lib/init/{util/indent.js → indent.js} +0 -0
  140. /package/lib/init/template/helm/{subcharts → files}/content-deployment/Chart.yaml +0 -0
  141. /package/lib/init/template/helm/{subcharts → files}/content-deployment/templates/_helpers.tpl +0 -0
  142. /package/lib/init/template/helm/{subcharts → files}/content-deployment/templates/job.yaml +0 -0
  143. /package/lib/init/template/helm/{subcharts → files}/content-deployment/templates/secret.yaml +0 -0
  144. /package/lib/init/template/helm/{subcharts → files}/content-deployment/templates/service-account.yaml +0 -0
  145. /package/lib/init/template/helm/{subcharts → files}/content-deployment/templates/service-binding.yaml +0 -0
  146. /package/lib/init/template/helm/{subcharts → files}/content-deployment/values.schema.json +0 -0
  147. /package/lib/init/template/helm/{subcharts → files}/content-deployment/values.yaml +0 -0
  148. /package/lib/init/template/helm/{subcharts → files}/service-instance/Chart.yaml +0 -0
  149. /package/lib/init/template/helm/{subcharts → files}/service-instance/templates/_helpers.tpl +0 -0
  150. /package/lib/init/template/helm/{subcharts → files}/service-instance/templates/serviceinstance.yaml +0 -0
  151. /package/lib/init/template/helm/{subcharts → files}/service-instance/values.schema.json +0 -0
  152. /package/lib/init/template/helm/{subcharts → files}/service-instance/values.yaml +0 -0
  153. /package/lib/init/template/helm/{subcharts → files}/web-application/templates/NOTES.txt +0 -0
  154. /package/lib/init/template/helm/{subcharts → files}/web-application/templates/_helpers.tpl +0 -0
  155. /package/lib/init/template/helm/{subcharts → files}/web-application/templates/api-rule.yaml +0 -0
  156. /package/lib/init/template/helm/{subcharts → files}/web-application/templates/deployment.yaml +0 -0
  157. /package/lib/init/template/helm/{subcharts → files}/web-application/templates/network-policy.yaml +0 -0
  158. /package/lib/init/template/helm/{subcharts → files}/web-application/templates/pod-disruption-budget.yaml +0 -0
  159. /package/lib/init/template/helm/{subcharts → files}/web-application/templates/rbac.yaml +0 -0
  160. /package/lib/init/template/helm/{subcharts → files}/web-application/templates/secret.yaml +0 -0
  161. /package/lib/init/template/helm/{subcharts → files}/web-application/templates/service-account.yaml +0 -0
  162. /package/lib/init/template/helm/{subcharts → files}/web-application/templates/service-binding.yaml +0 -0
  163. /package/lib/init/template/helm/{subcharts → files}/web-application/templates/service.yaml +0 -0
  164. /package/lib/init/template/helm/{subcharts → files}/web-application/templates/virtual-service.yaml +0 -0
  165. /package/lib/init/template/helm/{subcharts → files}/web-application/values.schema.json +0 -0
  166. /package/lib/init/template/{helm/chart → helm-unified-runtime/files}/templates/NOTES.txt +0 -0
  167. /package/lib/init/template/{helm/chart → helm-unified-runtime/files}/templates/_deployment_helpers.tpl +0 -0
  168. /package/lib/init/template/{helm/chart → helm-unified-runtime/files}/templates/_helpers.tpl +0 -0
  169. /package/lib/init/template/{helm/chart → helm-unified-runtime/files}/values.root.schema.json +0 -0
  170. /package/lib/init/template/{helm/chart → helm-unified-runtime/files}/values.schema.json +0 -0
  171. /package/lib/init/template/postgres/files/{mta.yaml.hbs → mta.yml.hbs} +0 -0
  172. /package/lib/init/{util/xml.js → xml.js} +0 -0
  173. /package/lib/{client/helper/env_helper.js → mtx/util/env.js} +0 -0
  174. /package/lib/{client/helper/fs_helper.js → mtx/util/fs.js} +0 -0
  175. /package/lib/{client/helper → mtx/util}/logging.js +0 -0
  176. /package/lib/{client/helper → mtx/util}/question.js +0 -0
  177. /package/lib/{client/helper/string_helper.js → mtx/util/strings.js} +0 -0
  178. /package/lib/util/{cfUtil.js → cf.js} +0 -0
  179. /package/lib/util/{gitUtil.js → git.js} +0 -0
package/lib/init/index.js CHANGED
@@ -5,17 +5,17 @@ const term = require('../util/term');
5
5
  const { OPTIONS, COMMAND_INIT, PROJECT_FILES, REGEX_PROJECT_NAME } = require('./constants')
6
6
  const {
7
7
  NODEJS, JAVA,
8
- HANA, APPROUTER, XSUAA, KIBANA, ENTERPRISE_MESSAGING,
8
+ HANA, APPROUTER, XSUAA, APPLICATION_LOGGING, ENTERPRISE_MESSAGING,
9
9
  MTA, MULTITENANCY, PIPELINE,
10
10
  TINY_SAMPLE, SAMPLE
11
11
  } = OPTIONS
12
12
 
13
- const cds = require('../cds'), { exists, readdir } = cds.utils
13
+ const cds = require('..'), { exists, readdir } = cds.utils
14
14
  const DEBUG = /\b(y|all|cli)\b/.test(process.env.DEBUG) ? console.debug : undefined
15
15
 
16
16
  module.exports = class CDSGenerator {
17
17
  constructor() {
18
- this.cwd = process.cwd();
18
+ this.cwd = process.cwd()
19
19
 
20
20
  this.uiConfig = [{
21
21
  title: 'Project Name',
@@ -60,7 +60,7 @@ module.exports = class CDSGenerator {
60
60
  { name: 'SAP HANA Cloud', value: HANA },
61
61
  { name: 'SAP Application Router', value: APPROUTER },
62
62
  { name: 'User Authentication via XSUAA', value: XSUAA },
63
- { name: 'Kibana Logging Dashboard', value: KIBANA },
63
+ { name: 'Application Logging', value: APPLICATION_LOGGING },
64
64
  { name: 'SAP BTP Event Mesh', value: ENTERPRISE_MESSAGING }
65
65
  ],
66
66
  multiple: true,
@@ -133,9 +133,12 @@ module.exports = class CDSGenerator {
133
133
  // keep caller independent from internal names
134
134
  options: {
135
135
  cwd: options.cwd,
136
- add: options.add
136
+ add: new Set(options.add)
137
137
  }
138
138
  }
139
+
140
+ // must be called after above code to ensure cds.cli.options.add is set
141
+ cds.add = require('./add')
139
142
  }
140
143
 
141
144
  this._initialize(projectName);
@@ -176,8 +179,8 @@ module.exports = class CDSGenerator {
176
179
  for (const template of this.templateList) {
177
180
  console.log(`Adding feature '${template.name}'...`);
178
181
  await template.run();
179
- await template.runDependentMerging();
180
- await template.runRelatedMerging();
182
+ await template.combine();
183
+ await template.combineSupported();
181
184
  }
182
185
 
183
186
  if (cds.cli.command === COMMAND_INIT) {
@@ -192,10 +195,10 @@ module.exports = class CDSGenerator {
192
195
  }
193
196
  }
194
197
 
195
- async _createTemplate(filename) {
198
+ async _createTemplate(plugin) {
196
199
  try {
197
- const TemplateClass = require(`./template/${filename}`);
198
- return new TemplateClass(this);
200
+ const Plugin = cds.add.registered.get(plugin) ?? require(`./template/${plugin}`)
201
+ return new Plugin(this)
199
202
  } catch (err) {
200
203
  if (err.code === 'MODULE_NOT_FOUND') {
201
204
  const entries = (await readdir(path.join(__dirname, 'template')))
@@ -205,11 +208,11 @@ module.exports = class CDSGenerator {
205
208
  entry !== 'nodejs' &&
206
209
  entry !== 'java'
207
210
  );
208
- const fuzzySearch = require('../../bin/util/fuzzySearch');
209
- const [bestMatch] = fuzzySearch(filename, entries);
210
- throw `Unknown facet '${term.bold(filename)}'. Did you mean ${term.bold(`cds add ${bestMatch}`)}?\n\nHaven't found the proper facet yet? Here are all supported facets:\n\n ${term.bold(entries.join('\n '))}\n`
211
+ const fuzzySearch = require('../../bin/util/fuzzySearch')
212
+ const [bestMatch] = fuzzySearch(plugin, entries)
213
+ throw `Unknown facet '${term.bold(plugin)}'. Did you mean ${term.bold(`cds add ${bestMatch}`)}?\n\nHaven't found the proper facet yet? Here are all supported facets:\n\n ${term.bold(entries.join('\n '))}\n`
211
214
  }
212
- throw err;
215
+ throw err
213
216
  }
214
217
  }
215
218
 
@@ -217,28 +220,39 @@ module.exports = class CDSGenerator {
217
220
  const templates = new Map
218
221
  const { command, options, argv } = cds.cli
219
222
 
220
- const facets = command === COMMAND_INIT ? options.add : new Set(argv[0].split(','))
223
+ const plugins = command === COMMAND_INIT ? options.add : new Set(argv[0].split(','))
221
224
 
222
- for (let facet of facets) {
225
+ for (let plugin of plugins) {
223
226
  // Compat
224
- if (facet === 'sample-tiny') facet = 'tiny-sample'
225
- if (facet === 'kibana-logging') facet = 'kibana'
226
- if (facet === 'postgresql') facet = 'postgres'
227
+ if (plugin === 'helm' && cds.cli.options['internal-unified-runtime-charts']) {
228
+ plugin = 'helm-unified-runtime'
229
+ cds.cli.options.add.delete('helm')
230
+ cds.cli.options.add.add('helm-unified-runtime')
231
+ }
232
+ if (plugin === 'sample-tiny') plugin = 'tiny-sample'
233
+ if (plugin in { 'kibana-logging':1, 'kibana':1 }) plugin = 'application-logging'
234
+ if (plugin === 'postgresql') plugin = 'postgres'
227
235
 
228
- if (!templates.has(facet)) {
229
- const template = await this._createTemplate(facet)
236
+ if (!templates.has(plugin)) {
237
+ const template = await this._createTemplate(plugin)
230
238
  if (await template.canRun()) {
231
239
  DEBUG?.(`Adding feature '${template.name}'`)
232
- templates.set(facet, template)
233
- const dependencies = await template.getDependencies()
234
- dependencies?.forEach(d => facets.add(d))
240
+ templates.set(plugin, template)
241
+ const dependencies = await template.requires()
242
+ dependencies?.forEach(d => plugins.add(d))
235
243
  }
236
244
  }
237
245
  }
238
246
 
239
247
  const cmds = Object.values(OPTIONS)
240
248
  this.templateList = Array.from(templates.entries())
241
- .sort(([a], [b]) => cmds.indexOf(a) - cmds.indexOf(b))
249
+ .sort(([lhs], [rhs]) => {
250
+ const a = cmds.indexOf(lhs), b = cmds.indexOf(rhs)
251
+ if (a === -1 && b === -1) return 0
252
+ if (a === -1) return 1
253
+ if (b === -1) return -1
254
+ return a - b
255
+ })
242
256
  .map((entry) => entry[1])
243
257
 
244
258
  // check debug to avoid lengthy computation when not in debug mode
@@ -1,20 +1,21 @@
1
1
  const YAML = require('@sap/cds-foss').yaml
2
- const { copy, read, write, exists } = require('../../cds').utils
3
- const { readYAML, writeYAML, readJSON } = require('../../util/fs')
2
+ const cds = require('../cds')
3
+ const { copy, read, write, exists } = cds.utils
4
+ const { readYAML, writeYAML, readJSON } = require('../util/fs')
4
5
  const { EOL } = require('node:os')
5
- const { detectIndent } = require('../util/indent')
6
+ const { detectIndent } = require('./indent')
6
7
 
7
8
  function _isObject(item) {
8
9
  return item && typeof item === 'object' && !Array.isArray(item)
9
10
  }
10
11
 
11
- function _deepMerge(target, source) {
12
+ function _mergeObject(target, source) {
12
13
  const unique = array => [...new Set(array.map(JSON.stringify))].map(JSON.parse)
13
14
  if (_isObject(target) && _isObject(source)) {
14
15
  for (const key in source) {
15
16
  if (_isObject(source[key])) {
16
17
  if (!target[key]) Object.assign(target, { [key]: source[key] })
17
- else _deepMerge(target[key], source[key])
18
+ else _mergeObject(target[key], source[key])
18
19
  } else if (Array.isArray(source[key]) && Array.isArray(target[key])) {
19
20
  target[key] = unique([...source[key], ...target[key]])
20
21
  } else {
@@ -56,9 +57,7 @@ async function mergeGitignore (into, from) {
56
57
  /**
57
58
  * @typedef {Object} MatchedNode
58
59
  * @property {string | MatchedNode} in - key path or matched node to start search from
59
- * @property {Object[]} where - list of constraints
60
- * @property {string} where[].property - property key path
61
- * @property {string} where[].isEqualTo - comparison string
60
+ * @property {Object} where - list of constraints
62
61
  */
63
62
 
64
63
  /**
@@ -97,7 +96,7 @@ async function mergeGitignore (into, from) {
97
96
  * @param {MergingSemantics} [semantics] - merging algorithm semantics
98
97
  * @returns {Promise<JSON>} - merged JSON
99
98
  */
100
- async function mergeJSON(into, from, project, semantics) {
99
+ async function _mergeJSON(into, from, project, semantics) {
101
100
  const source = typeof from === 'string' ? await readJSON(from, project) : from
102
101
 
103
102
  let result
@@ -107,11 +106,11 @@ async function mergeJSON(into, from, project, semantics) {
107
106
  const target = await read(into)
108
107
  const targetYAML = YAML.parseDocument(YAML.stringify(target))
109
108
  const sourceYAML = YAML.parseDocument(YAML.stringify(source))
110
- const resultYAML = await mergeYAML(targetYAML, sourceYAML, project, semantics)
109
+ const resultYAML = await _mergeYAML(targetYAML, sourceYAML, project, semantics)
111
110
  result = YAML.parse(YAML.stringify(resultYAML))
112
111
  } else {
113
112
  const target = typeof into === 'string' ? await read(into, 'json') : into
114
- result = _deepMerge(target, source)
113
+ result = _mergeObject(target, source)
115
114
  }
116
115
  // we need to stringify result on site to be able to apply the appropriate indentation.
117
116
  // cds-utils.write is currently hardcoded to use spaces for indentation.
@@ -130,7 +129,7 @@ async function mergeJSON(into, from, project, semantics) {
130
129
  * @returns {Promise<YAML.Document>} - merged YAML
131
130
  * @throws {Error} Throws an error if empty target YAML is passed in
132
131
  */
133
- async function mergeYAML(into, from, project, semantics = {}) {
132
+ async function _mergeYAML(into, from, project, semantics = {}) {
134
133
  const target = typeof into === 'string' ? await readYAML(into) : into instanceof YAML.Document ? into : new YAML.Document(into)
135
134
  const source = typeof from === 'string' ? await readYAML(from, project) : from instanceof YAML.Document ? from : new YAML.Document(from)
136
135
 
@@ -166,16 +165,14 @@ async function mergeYAML(into, from, project, semantics = {}) {
166
165
 
167
166
  const _validateWhere = (node, dict, index) => {
168
167
  const _addToMap = entries => entries?.filter(entry => {
169
- let where = entry.in
170
- if (typeof where === 'string') return true // just a key path on the document root
171
- else where = where[0] // a more complex list of constraints, a sequence/array is involved
172
- const neededParent = dict.get(where)?.node
168
+ if (typeof entry.in === 'string') return true // just a key path on the document root
169
+ const neededParent = dict.get(entry.in[0])?.node
173
170
  return collectionStack.includes(neededParent) // lookbehind in parent collection stack
174
171
  // REVISIT: Only look behind until sequence is reached?
175
172
  }).forEach(item => {
176
173
  const json = JSON.parse(String(node))
177
- const whereFulfilled = item.where.every(constraint =>
178
- constraint.isEqualTo === _getProperty(json, constraint.property)
174
+ const whereFulfilled = Object.entries(item.where).every(([key, value]) =>
175
+ _getProperty(json, key) === value
179
176
  )
180
177
  if (whereFulfilled) {
181
178
  const [collection] = collectionStack
@@ -415,11 +412,26 @@ async function removeFromYAMLArray(name, keyPath, aliasToRemove) {
415
412
  await writeYAML(name, yaml)
416
413
  }
417
414
 
415
+ const path = require('path')
416
+ const merge = (...src) => {
417
+ return { into: (dst, { with: withProject, project, ...semantics } = {}) => {
418
+ if (Object.keys(semantics).length === 0) semantics = undefined
419
+ project ??= withProject // to allow { with: project } for a more fluent API
420
+ const isYAML = typeof dst === 'string' && ['.yaml', '.yml', '.yaml.hbs', '.yml.hbs'].some(ext => dst.endsWith(ext))
421
+ const _merge = isYAML ? _mergeYAML : _mergeJSON
422
+ const target = typeof dst === 'string' ? path.resolve(cds.root, dst) : dst
423
+ if (typeof src[src.length-1] === 'string') {
424
+ return _merge(target, path.join(...src), project, semantics)
425
+ } else {
426
+ return _merge(target, ...src, project, semantics)
427
+ }
428
+ }}
429
+ }
430
+
418
431
  module.exports = {
432
+ merge,
419
433
  sort,
420
434
  mergeGitignore,
421
- mergeJSON,
422
- mergeYAML,
423
435
  removeFromYAML,
424
436
  removeFromYAMLArray
425
437
  }
@@ -1,16 +1,16 @@
1
1
 
2
- const { URLS, MAVEN_ARCHETYPE_VERSION, OPTIONS: { JAVA_MVN, JAVA_PACKAGE, SAMPLES, HANA }, REGEX_JAVA_PACKAGE } = require('../constants');
2
+ const { URLS, MAVEN_ARCHETYPE_VERSION, OPTIONS: { JAVA_MVN, JAVA_PACKAGE, SAMPLES, HANA }, REGEX_JAVA_PACKAGE } = require('./constants');
3
3
 
4
4
  const cp = require('child_process');
5
5
  const util = require('util');
6
6
  const execAsync = util.promisify(cp.exec);
7
- const term = require('../../util/term');
8
- const cmd = require('../../util/command');
7
+ const term = require('../util/term');
8
+ const cmd = require('../util/command');
9
9
 
10
10
  const DEFAULT_GROUP_ID = 'customer';
11
11
 
12
12
  const LOG = console
13
- const cds = require('../../cds')
13
+ const cds = require('../cds')
14
14
  const DEBUG = cds.debug('cli')
15
15
 
16
16
  class MvnArchetypeUtil {
@@ -58,7 +58,7 @@ class MvnArchetypeUtil {
58
58
  params.set(`com.sap.cds:cds-maven-plugin:${MAVEN_ARCHETYPE_VERSION}:add`);
59
59
  params.set(`-Dfeature`, feature)
60
60
  const profile = cds.cli.options.for
61
- params.set(`-Dprofile`, profile === 'production' ? 'cloud' : profile)
61
+ params.set(`-Dprofile`, profile === 'production' ? 'cloud' : profile ?? 'default')
62
62
 
63
63
  const { options } = cds.cli
64
64
  if (options[JAVA_MVN]) {
@@ -0,0 +1,85 @@
1
+ const { join } = require('path')
2
+ const cds = require('../cds')
3
+ const { readdir } = cds.utils
4
+ const DEBUG = cds.debug('add')
5
+ const { env4 } = require('./projectReader')
6
+
7
+ module.exports = class Plugin {
8
+
9
+ constructor() {
10
+ this.name = this.constructor.name.replace(/template/i, '').replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
11
+ }
12
+
13
+ /**
14
+ * Gets the required templates.
15
+ * @returns {string[]} An array of strings representing required plugins.
16
+ * Returns an empty array by default.
17
+ * In general, avoid such dependencies if possible.
18
+ */
19
+ requires() { return [] }
20
+
21
+ /**
22
+ * Determines whether a plugin can be run.
23
+ * @returns {Promise<boolean>} A promise that resolves to `true` if the template can be run.
24
+ */
25
+ async canRun() { return true }
26
+
27
+ /**
28
+ * Determines if the plugin has already been added to the project.
29
+ * @param {import('@sap/cds').env} env - The env for the 'production' profile.
30
+ * @returns {boolean} - Returns `true` if the template has been added, otherwise `false`.
31
+ */
32
+ static hasInProduction(env) { // eslint-disable-line no-unused-vars
33
+ return false
34
+ }
35
+
36
+ /**
37
+ * Runs the plugin.
38
+ */
39
+ async run() { }
40
+
41
+ /**
42
+ * Runs the combine operations for interoperability with other plugins.
43
+ */
44
+ async combine() { }
45
+
46
+
47
+
48
+
49
+
50
+ /* ––––––––––––
51
+ Yeoman Generator
52
+ –––––––––––– */
53
+
54
+ /**
55
+ * Runs the finalize operations.
56
+ */
57
+ async finalize() { } // REVISIT: Required for yo-generator, check if needed any more?
58
+
59
+ /* ––––––––––––
60
+ Private APIs
61
+ –––––––––––– */
62
+
63
+ async combineSupported() {
64
+ DEBUG && console.log()
65
+ DEBUG && console.log('Plugins active for production:')
66
+ const fromDk = await readdir(join(__dirname, './template'))
67
+ const { registered } = require('./add')
68
+ const processPlugins = async (plugins) => {
69
+ for (const plugin of plugins) {
70
+ const Plugin = registered.get(plugin) ?? require('./template/' + plugin)
71
+ const template = new Plugin()
72
+ const inProduction = Plugin.hasInProduction(env4('production'))
73
+ DEBUG && console.log(inProduction ? '✅' : '❌', plugin)
74
+ if (inProduction) await template.combine()
75
+ }
76
+ }
77
+
78
+ if (DEBUG) console.log(`\n ${fromDk.length} from @sap/cds-dk:`)
79
+ await processPlugins(fromDk)
80
+ if (DEBUG) console.log(`\n ${registered.size} from plugins:`)
81
+ await processPlugins(registered.keys())
82
+
83
+ DEBUG && console.log()
84
+ }
85
+ }
@@ -0,0 +1,145 @@
1
+ const cds = require('../cds')
2
+ const { join, resolve, basename } = require('path')
3
+ const { fs, exists, isdir } = cds.utils
4
+ const { parseXml } = require('./xml')
5
+ const { REGEX_JAVA_VERSION } = require('./constants')
6
+ const DEBUG = cds.debug('cli')
7
+
8
+ module.exports = new class ProjectReader {
9
+
10
+ constructor() {
11
+ this.readProject = this.readProject.bind(this)
12
+
13
+ // 1. Use project name and static default values
14
+ this.appVersion = cds.cli.options.add?.has('java') ? '1.0.0-SNAPSHOT' : '1.0.0'
15
+ this.appName = this.appId = basename(cds.root)
16
+ this.appDescription = 'A simple CAP project.'
17
+
18
+ // 2. Use either pom.xml or package.json
19
+ if (exists('pom.xml')) {
20
+ const { artifactId = [], description = [], version = [], properties = [] } = parseXml(resolve(cds.root, 'pom.xml')) ?? {}
21
+ const v = version[0] === '${revision}' ? properties[0]?.revision[0] : version[0]
22
+ if (v && REGEX_JAVA_VERSION.test(v)) this.appVersion = v
23
+ if (artifactId[0]) this.appName = this.appId = artifactId[0].split(/-parent/)[0]
24
+ if (description[0]) this.appDescription = description[0]
25
+ }
26
+ else if (exists('package.json')) {
27
+ const { name, version, description } = JSON.parse(fs.readFileSync(join(cds.root, 'package.json')))
28
+ this.appVersion = version
29
+ const segments = name.trim().replace(/@/g, '').split('/').map(encodeURIComponent)
30
+ this.appName = segments[segments.length - 1]
31
+ this.appId = segments.join('.')
32
+ this.appDescription = description ?? this.appDescription
33
+ }
34
+ }
35
+
36
+ // REVISIT: There should an API provided by cds.env
37
+ /**
38
+ * Returns cds.env using 'production' profile by default as mta deployment is executed with having production profile set.
39
+ */
40
+ env4(profile = 'production') {
41
+ const cdsEnv = process.env.CDS_ENV
42
+ process.env.CDS_ENV = profile
43
+ try {
44
+ return cds.env.for('cds')
45
+ } finally {
46
+ cdsEnv ? process.env.CDS_ENV = cdsEnv : delete process.env.CDS_ENV
47
+ }
48
+ }
49
+
50
+ /**
51
+ * @typedef {Object} ProjectConfig
52
+ * @property {string} appVersion The version of the application.
53
+ * @property {string} appName The name of the application.
54
+ * @property {string} appId The ID of the application.
55
+ * @property {string} appDescription The description of the application.
56
+ * @property {string} language The programming language of the project.
57
+ * @property {string} srvPath The path to the server module.
58
+ * @property {string} archiveName The name of the archive.
59
+ * @property {Object} db Database configuration.
60
+ * @property {string} configFile The name of the configuration file. This is typically the `package.json` for Node.js and `.cdsrc.json` for Java.
61
+ * @property {string} appPath The path to the app folder.
62
+ * @property {boolean} hasUIEmbedded Indicates if the project uses an embedded UI.
63
+ * @property {boolean} hasUIModule Indicates if the project is using a modular UI.
64
+ * @property {boolean} hasUI Indicates if the project has a UI.
65
+ * @property {boolean} isJava Indicates if the project is using Java.
66
+ * @property {boolean} isNodejs Indicates if the project is using Node.js.
67
+ * @property {boolean} hasHelmUnifiedRuntime Indicates if the project uses Unified Runtime Helm deployment.
68
+ * @property {boolean} hasHelm Indicates if the project uses CAP Helm deployment.
69
+ * @property {boolean} hasMta Indicates if the project uses MTA deployment.
70
+ * @property {boolean} hasConnectivity Indicates if the project uses connectivity.
71
+ * @property {boolean} hasMultitenancy Indicates if the project is multitenant.
72
+ * @property {boolean} hasToggles Indicates if the project uses feature toggles.
73
+ * @property {boolean} hasExtensibility Indicates if the project is extensible.
74
+ * @property {boolean} hasXsuaa Indicates if the project uses XSUAA.
75
+ * @property {boolean} hasHana Indicates if the project uses HANA.
76
+ * @property {boolean} hasPostgres Indicates if the project uses PostgreSQL.
77
+ * @property {boolean} hasEnterpriseMessaging Indicates if the project uses SAP BTP Event Mesh.
78
+ * @property {boolean} hasApprouter Indicates if the project uses the SAP Application Router.
79
+ * @property {boolean} hasHtml5Repo Indicates if the project uses the SAP BTP HTML5 Application Repository.
80
+ * @property {boolean} hasDestination Indicates if the project uses the SAP BTP Destination service.
81
+ *
82
+ */
83
+
84
+ /**
85
+ * Reads the project configuration and constructs a project object.
86
+ * This function dynamically resolves project properties and returns an object with these properties.
87
+ *
88
+ * @returns {ProjectConfig} An object representing the project configuration.
89
+ */
90
+ readProject() {
91
+ const env = this.env4('production')
92
+ const { appVersion, appName, appId, appDescription } = this
93
+ DEBUG?.({ env })
94
+
95
+ const _inProd = plugin => require(`./template/${plugin}`).hasInProduction(env)
96
+ const reserved = {
97
+ has: x => _inProd(x),
98
+ shortcut: () => true,
99
+ profile: () => cds.cli.options?.for,
100
+ language: () => exists('pom.xml') ? 'java' : 'nodejs',
101
+ isJava: () => exists('pom.xml') || cds.cli.options?.add?.has('java'),
102
+ isNodejs: () => !exists('pom.xml') && !cds.cli.options?.add?.has('java'),
103
+ srvPath: () => join(env.build.target, env.folders.srv.replace(/\/$/, '')).replace(/\\/g, '/'),
104
+ archiveName: () => {
105
+ const pom = parseXml(resolve(cds.root, env.folders.srv, 'pom.xml'))
106
+ return (pom?.artifactId?.[0] ?? basename(cds.root)) + '-exec.' + pom?.packaging?.[0]
107
+ },
108
+ db: () => {
109
+ const folder = env.folders.db
110
+ const name = folder.replace(/\/$/, '')
111
+ const path = join(env.build.target, name).replace(/\\/g, '/')
112
+ return { folder, name, path }
113
+ },
114
+ configFile: () => exists('pom.xml') ? '.cdsrc.json' : 'package.json',
115
+ appPath: () => env.folders.app,
116
+ appVersion: () => appVersion,
117
+ appName: () => appName,
118
+ cleanedAppName: () => appName.replaceAll('_', '-'),
119
+ appId: () => appId,
120
+ appDescription: () => appDescription,
121
+ hasUIEmbedded: () => exists(join(env.folders.app, 'xs-app.json')),
122
+ hasUIModule: () => !this.hasUIEmbedded && isdir(env.folders.app) && fs.readdirSync(resolve(cds.root, env.folders.app)).length > 0,
123
+ hasUI: () => this.hasUIEmbedded || this.hasUIModule
124
+ }
125
+
126
+ const project = (() => {
127
+ const defined = {}
128
+ return new Proxy({}, {
129
+ get(_, p) { return _get(p) ?? defined[p] },
130
+ set(_, p, value) { defined[p] = value; return true },
131
+ has(_, p) { return _get(p) ?? p in defined }
132
+ })
133
+ })()
134
+
135
+ const _get = property => { const p = property
136
+ if (p in reserved) return reserved[p]()
137
+ for (const prefix of ['has', 'is']) if (p.startsWith(prefix) && p.length > prefix.length) {
138
+ return _inProd(p.slice(prefix.length).replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase())
139
+ }
140
+ }
141
+
142
+ DEBUG?.({ project })
143
+ return project
144
+ }
145
+ }
@@ -0,0 +1,151 @@
1
+ // Registry of sequence matchers in mta.yaml files
2
+ const cds = require('../../cds')
3
+
4
+ const srvNode4 = path => ({ in: 'modules', where: { type: 'nodejs', path } })
5
+ const srvJava4 = path => ({ in: 'modules', where: { type: 'java', path } })
6
+ const srv4 = cds.env['project-nature'] === 'java' || cds.cli.options?.add?.has('java') ? srvJava4 : srvNode4
7
+
8
+ const approuter = { in: 'modules', where: { type: 'approuter.nodejs' } }
9
+
10
+ const xsuaa = {
11
+ in: 'resources',
12
+ where: { type: 'org.cloudfoundry.managed-service', 'parameters.service': 'xsuaa' }
13
+ }
14
+
15
+ const destination = {
16
+ in: 'resources',
17
+ where: { type: 'org.cloudfoundry.managed-service', 'parameters.service': 'destination' }
18
+ }
19
+
20
+ const connectivity = {
21
+ in: 'resources',
22
+ where: { type: 'org.cloudfoundry.managed-service', 'parameters.service': 'connectivity' }
23
+ }
24
+
25
+ const redis = {
26
+ in: 'resources',
27
+ where: { type: 'org.cloudfoundry.managed-service', 'parameters.service': 'redis-cache' }
28
+ }
29
+
30
+ const enterpriseMessaging = {
31
+ in: 'resources',
32
+ where: { type: 'org.cloudfoundry.managed-service', 'parameters.service': 'enterprise-messaging' }
33
+ }
34
+
35
+ const applicationLogging = {
36
+ in: 'resources',
37
+ where: { type: 'org.cloudfoundry.managed-service', 'parameters.service': 'application-logs' }
38
+ }
39
+
40
+ const hdbDeployer = {
41
+ in: 'modules',
42
+ where: { type: 'hdb' }
43
+ }
44
+
45
+ const hdiContainer = {
46
+ in: 'resources',
47
+ where: { type: 'com.sap.xs.hdi-container' }
48
+ }
49
+
50
+ const notificationTypesDeployer = {
51
+ in: 'modules',
52
+ where: { type: 'nodejs', name: 'notification-content-deployment' }
53
+ }
54
+
55
+ const serviceManager = {
56
+ in: 'resources',
57
+ where: { type: 'org.cloudfoundry.managed-service', 'parameters.service': 'service-manager', 'parameters.service-plan': 'container' }
58
+ }
59
+
60
+ const postgres = {
61
+ in: 'resources',
62
+ where: { 'parameters.service': 'postgresql-db' }
63
+ }
64
+
65
+ const postgresDeployer = {
66
+ in: 'modules',
67
+ where: { type: 'nodejs', path: 'gen/pg' }
68
+ }
69
+
70
+ const saasRegistry = {
71
+ in: 'resources',
72
+ where: { type: 'org.cloudfoundry.managed-service', 'parameters.service': 'saas-registry' }
73
+ }
74
+
75
+ const srvApi4 = srv => ({
76
+ in: [srv, 'provides'],
77
+ where: { name: 'srv-api', 'properties.srv-url': '${default-url}' }
78
+ })
79
+
80
+ const mtxSidecar4 = path => ({
81
+ in: 'modules',
82
+ where: { type: 'nodejs', path }
83
+ })
84
+
85
+ const providedMtxApiSidecar4 = mtxSidecar => ({
86
+ in: [mtxSidecar, 'provides'],
87
+ where: { name: 'mtx-api', 'properties.mtx-url': '${default-url}' }
88
+ })
89
+
90
+ const requiredMtxApi = {
91
+ in: [approuter, 'requires'],
92
+ where: { 'properties.name': 'mtx-api', 'properties.url': '~{mtx-url}' }
93
+ }
94
+
95
+ const providedAppApi = {
96
+ in: [approuter, 'provides'],
97
+ where: { name: 'app-api', 'properties.app-protocol': '${protocol}', 'properties.app-uri': '${default-uri}' }
98
+ }
99
+
100
+ const requiredAppApi4 = module => ({
101
+ in: [module, 'requires'],
102
+ where: { name: 'app-api', 'properties.SUBSCRIPTION_URL': '~{app-protocol}://\\${tenant_subdomain}-~{app-uri}' }
103
+ })
104
+
105
+ const requiredMtxSidecarApi4 = srv => ({
106
+ in: [srv, 'requires'],
107
+ where: { 'properties.CDS_MULTITENANCY_SIDECAR_URL': '~{mtx-url}' }
108
+ })
109
+
110
+ const providedJavaApprouterApi4 = srv => ({
111
+ in: [srv, 'requires'],
112
+ where: { 'properties.CDS_MULTITENANCY_APPUI_URL': '~{url}' }
113
+ })
114
+
115
+ const providedJavaApprouterApi = {
116
+ in: [approuter, 'requires'],
117
+ where: { 'properties.url': '${default-url}' }
118
+ }
119
+
120
+ const approuterExtensibility = {
121
+ in: 'routes',
122
+ where: { source: '^/-/cds/.*', destination: 'mtx-api', authenticationType: 'none' }
123
+ }
124
+
125
+ module.exports = {
126
+ srv4,
127
+ approuter,
128
+ xsuaa,
129
+ destination,
130
+ connectivity,
131
+ enterpriseMessaging,
132
+ hdbDeployer,
133
+ hdiContainer,
134
+ notificationTypesDeployer,
135
+ serviceManager,
136
+ postgres,
137
+ postgresDeployer,
138
+ saasRegistry,
139
+ redis,
140
+ srvApi4,
141
+ mtxSidecar4,
142
+ providedMtxApiSidecar4,
143
+ requiredMtxApi,
144
+ requiredMtxSidecarApi4,
145
+ providedAppApi,
146
+ requiredAppApi4,
147
+ providedJavaApprouterApi4,
148
+ providedJavaApprouterApi,
149
+ applicationLogging,
150
+ approuterExtensibility
151
+ }