@sap/cds 6.0.3 → 6.1.1

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 (131) hide show
  1. package/CHANGELOG.md +165 -18
  2. package/apis/cds.d.ts +11 -7
  3. package/apis/log.d.ts +46 -0
  4. package/apis/ql.d.ts +72 -15
  5. package/bin/build/buildTaskHandler.js +5 -2
  6. package/bin/build/constants.js +4 -1
  7. package/bin/build/provider/buildTaskHandlerEdmx.js +11 -39
  8. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +14 -34
  9. package/bin/build/provider/buildTaskHandlerInternal.js +56 -4
  10. package/bin/build/provider/buildTaskProviderInternal.js +22 -14
  11. package/bin/build/provider/hana/index.js +12 -9
  12. package/bin/build/provider/java/index.js +18 -8
  13. package/bin/build/provider/mtx/index.js +7 -4
  14. package/bin/build/provider/mtx/resourcesTarBuilder.js +60 -35
  15. package/bin/build/provider/mtx-extension/index.js +57 -0
  16. package/bin/build/provider/mtx-sidecar/index.js +46 -18
  17. package/bin/build/provider/nodejs/index.js +34 -13
  18. package/bin/deploy/to-hana/cfUtil.js +7 -2
  19. package/bin/deploy/to-hana/hana.js +20 -25
  20. package/bin/deploy/to-hana/hdiDeployUtil.js +13 -2
  21. package/bin/serve.js +7 -4
  22. package/lib/compile/{index.js → cds-compile.js} +0 -0
  23. package/lib/compile/extend.js +15 -5
  24. package/lib/compile/minify.js +1 -15
  25. package/lib/compile/parse.js +1 -1
  26. package/lib/compile/resolve.js +2 -2
  27. package/lib/compile/to/srvinfo.js +6 -4
  28. package/lib/{deploy.js → dbs/cds-deploy.js} +9 -8
  29. package/lib/env/{index.js → cds-env.js} +1 -17
  30. package/lib/env/{requires.js → cds-requires.js} +24 -3
  31. package/lib/env/defaults.js +7 -1
  32. package/lib/env/schemas/cds-package.json +11 -0
  33. package/lib/env/schemas/cds-rc.json +614 -0
  34. package/lib/index.js +19 -16
  35. package/lib/log/{errors.js → cds-error.js} +1 -1
  36. package/lib/log/{index.js → cds-log.js} +0 -0
  37. package/lib/ql/Query.js +9 -3
  38. package/lib/ql/SELECT.js +2 -2
  39. package/lib/ql/{index.js → cds-ql.js} +0 -9
  40. package/lib/req/context.js +49 -17
  41. package/lib/req/locale.js +5 -1
  42. package/lib/{serve → srv}/adapters.js +23 -19
  43. package/lib/{connect → srv}/bindings.js +0 -0
  44. package/lib/{connect/index.js → srv/cds-connect.js} +1 -1
  45. package/lib/{serve/index.js → srv/cds-serve.js} +1 -1
  46. package/lib/{serve → srv}/factory.js +1 -1
  47. package/lib/{serve/Service-api.js → srv/srv-api.js} +22 -6
  48. package/lib/{serve/Service-dispatch.js → srv/srv-dispatch.js} +13 -8
  49. package/lib/{serve/Service-handlers.js → srv/srv-handlers.js} +10 -0
  50. package/lib/{serve/Service-methods.js → srv/srv-methods.js} +10 -8
  51. package/lib/srv/srv-models.js +207 -0
  52. package/lib/{serve/Transaction.js → srv/srv-tx.js} +57 -40
  53. package/lib/utils/{tests.js → cds-test.js} +2 -2
  54. package/lib/utils/cds-utils.js +146 -0
  55. package/lib/utils/index.js +2 -145
  56. package/lib/utils/jest.js +43 -0
  57. package/lib/utils/resources/index.js +15 -25
  58. package/lib/utils/resources/tar.js +18 -41
  59. package/libx/_runtime/auth/index.js +14 -11
  60. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +7 -19
  61. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -4
  62. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -4
  63. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -4
  64. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -4
  65. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -2
  66. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +6 -19
  67. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -4
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -4
  70. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +0 -2
  71. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +3 -1
  72. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +38 -4
  73. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -3
  74. package/libx/_runtime/cds-services/services/utils/differ.js +4 -0
  75. package/libx/_runtime/cds-services/util/errors.js +1 -29
  76. package/libx/_runtime/common/i18n/messages.properties +2 -1
  77. package/libx/_runtime/common/perf/index.js +10 -15
  78. package/libx/_runtime/common/utils/binary.js +3 -4
  79. package/libx/_runtime/common/utils/cqn2cqn4sql.js +0 -1
  80. package/libx/_runtime/common/utils/entityFromCqn.js +8 -5
  81. package/libx/_runtime/common/utils/resolveView.js +1 -1
  82. package/libx/_runtime/common/utils/template.js +1 -1
  83. package/libx/_runtime/db/Service.js +2 -14
  84. package/libx/_runtime/db/expand/expandCQNToJoin.js +28 -25
  85. package/libx/_runtime/db/expand/rawToExpanded.js +7 -6
  86. package/libx/_runtime/db/generic/input.js +8 -1
  87. package/libx/_runtime/db/sql-builder/SelectBuilder.js +37 -18
  88. package/libx/_runtime/extensibility/activate.js +47 -47
  89. package/libx/_runtime/extensibility/add.js +22 -13
  90. package/libx/_runtime/extensibility/addExtension.js +17 -13
  91. package/libx/_runtime/extensibility/defaults.js +25 -30
  92. package/libx/_runtime/extensibility/handler/transformREAD.js +20 -18
  93. package/libx/_runtime/extensibility/linter/allowlist_checker.js +373 -0
  94. package/libx/_runtime/extensibility/linter/annotations_checker.js +113 -0
  95. package/libx/_runtime/extensibility/linter/checker_base.js +20 -0
  96. package/libx/_runtime/extensibility/linter/namespace_checker.js +180 -0
  97. package/libx/_runtime/extensibility/linter.js +32 -0
  98. package/libx/_runtime/extensibility/push.js +77 -20
  99. package/libx/_runtime/extensibility/service.js +29 -12
  100. package/libx/_runtime/extensibility/token.js +56 -0
  101. package/libx/_runtime/extensibility/utils.js +8 -6
  102. package/libx/_runtime/extensibility/validation.js +6 -9
  103. package/libx/_runtime/fiori/generic/new.js +0 -11
  104. package/libx/_runtime/hana/Service.js +0 -1
  105. package/libx/_runtime/hana/conversion.js +12 -1
  106. package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +4 -3
  107. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -0
  108. package/libx/_runtime/hana/pool.js +6 -10
  109. package/libx/_runtime/hana/search2Contains.js +0 -5
  110. package/libx/_runtime/hana/search2cqn4sql.js +1 -0
  111. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  112. package/libx/_runtime/messaging/service.js +11 -6
  113. package/libx/_runtime/remote/utils/data.js +5 -0
  114. package/libx/_runtime/sqlite/Service.js +7 -6
  115. package/libx/_runtime/sqlite/execute.js +41 -28
  116. package/libx/odata/afterburner.js +79 -2
  117. package/libx/odata/cqn2odata.js +9 -7
  118. package/libx/odata/grammar.pegjs +157 -76
  119. package/libx/odata/index.js +9 -3
  120. package/libx/odata/parser.js +1 -1
  121. package/libx/odata/utils.js +39 -5
  122. package/libx/rest/RestAdapter.js +3 -7
  123. package/libx/rest/middleware/delete.js +4 -5
  124. package/libx/rest/middleware/parse.js +3 -2
  125. package/package.json +3 -3
  126. package/server.js +1 -1
  127. package/srv/extensibility-service.cds +6 -3
  128. package/srv/model-provider.cds +3 -1
  129. package/srv/model-provider.js +84 -104
  130. package/srv/mtx.js +7 -1
  131. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +0 -240
@@ -2,7 +2,7 @@ const fs = require('fs')
2
2
  const path = require('path')
3
3
  const BuildTaskHandler = require('../buildTaskHandler')
4
4
  const { hasOptionValue } = require('../util')
5
- const { FOLDER_GEN } = require('../constants')
5
+ const { FOLDER_GEN, BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY } = require('../constants')
6
6
 
7
7
  class BuildTaskHandlerInternal extends BuildTaskHandler {
8
8
  /**
@@ -47,7 +47,7 @@ class BuildTaskHandlerInternal extends BuildTaskHandler {
47
47
  // the build results have already been deleted by the BuildTaskEngine if the build.target !== '.'
48
48
  // make sure that src is not a subfolder of dest
49
49
  if (this._buildOptions.root === this._buildOptions.target && this.task.src !== this.task.dest && !this._isSubDirectory(this.task.dest, this.task.src)) {
50
- await fs.promises.rm(this.task.dest, {force: true, recursive: true})
50
+ await fs.promises.rm(this.task.dest, { force: true, recursive: true })
51
51
  }
52
52
  }
53
53
 
@@ -164,8 +164,60 @@ class BuildTaskHandlerInternal extends BuildTaskHandler {
164
164
  )
165
165
  }
166
166
 
167
- options() {
168
- return { messages: this._messages }
167
+ async compileToJson(model, csnFile) {
168
+ // This will als add a @source prop containing the relative path to the origin .cds source file
169
+ // and a parsed _where clause for @restrict.{grant,where} annotations.
170
+ // The @source annotation is required for correct custom handler resolution if no @impl annotation has been defined as
171
+ // custom service handler implementations are relative to the origin .cds source files.
172
+ // For staging builds (task.src !== task.dest) the csn.json file that is served at runtime is copied into a corresponding srv subfolder.
173
+ // As a consequence the src folder name has to be included in the @source file name while for inplace builds (task.src === task.dest) this is not the case.
174
+ // This ensures that the paths are relative to the cwd when executing cds run.
175
+ const jsonOptions = {
176
+ cwd: this.buildOptions.root,
177
+ src: this.task.src === this.task.dest ? this.task.src : this.buildOptions.root
178
+ }
179
+ const csnStr = this.cds.compile.to.json(model, jsonOptions)
180
+ if (!this.hasBuildOption(BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY)) {
181
+ await this.write(csnStr).to(csnFile)
182
+ }
183
+ return csnStr
184
+ }
185
+
186
+ /**
187
+ * Collect and write language bundles into a single i18n.json file.
188
+ * @param {Object} model
189
+ * @param {string} bundleDest
190
+ */
191
+ async collectLanguageBundles(model, bundleDest) {
192
+ // collect effective i18n properties...
193
+ let bundles = {}
194
+ const bundleGenerator = this.cds.localize.bundles4(model)
195
+ if (bundleGenerator && bundleGenerator[Symbol.iterator]) {
196
+ for (let [locale, bundle] of bundleGenerator) {
197
+ // fallback bundle has the name ""
198
+ if (typeof locale === 'string') {
199
+ bundles[locale] = bundle
200
+ }
201
+ }
202
+ }
203
+
204
+ // omit bundles in case the fallback bundle is the only existing entry
205
+ const keys = Object.keys(bundles)
206
+ if (keys.length === 1 && keys[0] === "" && Object.keys(bundles[keys[0]]).length === 0) {
207
+ bundles = {}
208
+ }
209
+ // copied from ../compile/i18n.js
210
+ const { folders = ['i18n'], file: base = 'i18n' } = this.env.i18n
211
+ const file = path.join(bundleDest, folders[0], base + '.json')
212
+
213
+ // bundleDest might be null
214
+ if (bundleDest && Object.keys(bundles).length > 0) {
215
+ if (!this.hasBuildOption(BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY)) {
216
+ await this.write(bundles).to(file)
217
+ }
218
+ return { file, bundles }
219
+ }
220
+ return null
169
221
  }
170
222
 
171
223
  _isSubDirectory(parent, child) {
@@ -4,7 +4,7 @@ const { hasJavaNature, getProperty, isStreamlinedMtx } = require('../util')
4
4
  const BuildTaskProvider = require('../buildTaskProvider')
5
5
 
6
6
  const { FILE_EXT_CDS, BUILD_TASK_HANA, BUILD_TASK_FIORI, BUILD_TASK_JAVA, BUILD_TASK_JAVA_CF, BUILD_TASK_NODEJS, BUILD_TASK_NODE_CF, BUILD_TASK_MTX,
7
- BUILD_TASK_PREFIX, BUILD_TASKS, BUILD_TASK_MTX_SIDECAR, MTX_SIDECAR_FOLDER } = require("../constants")
7
+ BUILD_TASK_PREFIX, BUILD_TASKS, BUILD_TASK_MTX_SIDECAR, MTX_SIDECAR_FOLDER, BUILD_TASK_MTX_EXTENSION } = require("../constants")
8
8
 
9
9
  class BuildTaskProviderInternal extends BuildTaskProvider {
10
10
  constructor(cds, logger) {
@@ -57,6 +57,7 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
57
57
  case BUILD_TASK_FIORI:
58
58
  task.src = BuildTaskProviderInternal._normalizePath(this.env.folders.app)
59
59
  break
60
+ case BUILD_TASK_MTX_EXTENSION:
60
61
  case BUILD_TASK_MTX:
61
62
  task.src = "."
62
63
  break
@@ -112,12 +113,9 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
112
113
  }
113
114
  }
114
115
  } else {
115
- if (!db) {
116
- this.logger.log(`project doesn't have a database module [${this.env.folders.db}]`)
117
- }
118
- if (!srv) {
119
- this.logger.log(`project doesn't have a service module '${this.env.folders.srv}'`)
120
- }
116
+ !db && this.logger._debug && this.logger.debug(`project doesn't have a database module '${this.env.folders.db}'`)
117
+ !srv && this.logger._debug && this.logger.debug(`project doesn't have a service module '${this.env.folders.srv}'`)
118
+
121
119
  // create hana build task
122
120
  const dbTask = this._createDbTask(projectPath, db, dbOptions, buildOptions)
123
121
  if (dbTask) {
@@ -138,7 +136,7 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
138
136
 
139
137
  _createDbTask(projectPath, src, taskOptions, buildOptions) {
140
138
  this.logger.debug("determining database kind.")
141
- if (!src) {
139
+ if (!src || BuildTaskProviderInternal._isExtension(this.env)) {
142
140
  return null
143
141
  }
144
142
  let task = null
@@ -190,7 +188,7 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
190
188
  if (this.env.requires["cds.xt.ModelProviderService"]) {
191
189
  // sidecar scenario for Nodejs or Java
192
190
  const sidecarEnv = this.env.for("cds", path.join(projectPath, MTX_SIDECAR_FOLDER))
193
- if (this._isSidecar(sidecarEnv)) {
191
+ if (BuildTaskProviderInternal._isSidecar(sidecarEnv)) {
194
192
  task = {
195
193
  for: BUILD_TASK_MTX_SIDECAR
196
194
  }
@@ -205,6 +203,12 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
205
203
  }
206
204
  this.logger.debug("Nodejs MTX scenario without sidecar")
207
205
  }
206
+ } else if (BuildTaskProviderInternal._isExtension(this.env)) {
207
+ // streamlined mtx extension project
208
+ task = {
209
+ for: BUILD_TASK_MTX_EXTENSION
210
+ }
211
+ this.logger.debug("Streamlined MTX extension")
208
212
  } else if (this.env.get("requires.multitenancy") && isNode) {
209
213
  // classic mtx
210
214
  task = {
@@ -219,11 +223,11 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
219
223
 
220
224
  _createSrvTask(projectPath, src, taskOptions) {
221
225
  this.logger.debug("determining implementation technology")
222
- if (!src) {
226
+ if (!src || BuildTaskProviderInternal._isExtension(this.env)) {
223
227
  return null
224
228
  }
225
229
  let task = null
226
- if (this._hasJavaNature(projectPath, src)) {
230
+ if (BuildTaskProviderInternal._hasJavaNature(projectPath, src)) {
227
231
  task = this._createJavaTask(projectPath, src, taskOptions)
228
232
  } else {
229
233
  task = this._createNodeTask(src, taskOptions)
@@ -255,8 +259,12 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
255
259
  }
256
260
  }
257
261
 
258
- _isSidecar(env) {
259
- return env.requires['cds.xt.ModelProviderService']?.root
262
+ static _isSidecar(env) {
263
+ return env.requires['cds.xt.ModelProviderService']?.kind === "in-sidecar"
264
+ }
265
+
266
+ static _isExtension(env) {
267
+ return !!env.extends
260
268
  }
261
269
 
262
270
  /**
@@ -264,7 +272,7 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
264
272
  * @param {string} projectPath - the absolute project path
265
273
  * @param {string} src - the folder name of the service module
266
274
  */
267
- _hasJavaNature(projectPath, src) {
275
+ static _hasJavaNature(projectPath, src) {
268
276
  return hasJavaNature([path.join(projectPath, src), projectPath])
269
277
  }
270
278
 
@@ -22,6 +22,8 @@ const FILE_NAME_PACKAGE_JSON = "package.json"
22
22
  const PATH_LAST_DEV_CSN = "last-dev/csn.json"
23
23
  const FILE_NAME_UNDEPLOY_JSON = "undeploy.json"
24
24
 
25
+ const DEPLOY_FORMAT = "deploy-format"
26
+
25
27
  // add well-known types supported by HANA Cloud Edition - see also https://github.wdf.sap.corp/cap/issues/issues/8056
26
28
  const REQUIRED_PLUGIN_TYPES = [FILE_EXT_CSV, FILE_EXT_HDBTABLEDATA, FILE_EXT_HDBTABLE, ".hdbview", ".hdbindex", ".hdbconstraint"]
27
29
  class HanaModuleBuilder extends BuildTaskHandlerInternal {
@@ -91,22 +93,24 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
91
93
  /**
92
94
  * Copies all files located at <src> (except HANA artefacts not contained in <db>/src/**) to the folder <dest>.
93
95
  * '*.csv' files are read based on the corresponding CDS model file location and copied as flat list into folder '<dest>/src/gen>'.
94
- *
95
- * @param {string} src
96
- * @param {string} dest
97
- * @param {Object} model
96
+ *
97
+ * @param {string} src
98
+ * @param {string} dest
99
+ * @param {Object} model
98
100
  */
99
101
  async _copyResourcesExt(src, dest, model) {
100
- const deploy = require('../../../../lib/deploy')
101
- const resources = Object.keys(await deploy.resources(model))
102
+ const resources = Object.keys(await this.cds.deploy.resources(model))
102
103
  const csvPath = path.join(src, "data")
103
104
  // determine subfolder name used by the application for backward compatibility
104
105
  const csvFolder = resources.some(res => res && res.startsWith(csvPath)) ? "data" : "csv"
105
106
 
106
107
  // 1. copy csv files into 'src/gen/data' or 'src/gen/csv' subfolder
107
108
  const promises = []
109
+ const dbSrc = path.join(src, 'src')
110
+
108
111
  resources.forEach(res => {
109
- if (res && /\.csv$/.test(res)) {
112
+ // do not duplicate resources that are already contained in db/src/**
113
+ if (res && /\.csv$/.test(res) && !res.startsWith(dbSrc)) {
110
114
  promises.push(this.copy(res).to(path.join(this.task.options.compileDest, csvFolder, path.basename(res))))
111
115
  }
112
116
  })
@@ -117,7 +121,6 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
117
121
  blockList += this.hasBuildOption(CONTENT_ENV, false) ? "|\\.env($|\\..*$)" : ""
118
122
  blockList += this.hasBuildOption(CONTENT_DEFAULT_ENV_JSON, false) ? "|default-env\\.json$" : ""
119
123
  blockList = new RegExp(blockList)
120
- const dbSrc = path.join(src, 'src')
121
124
 
122
125
  await this.copyNativeContent(src, dest, (entry) => {
123
126
  if (entry.startsWith(dbSrc)) {
@@ -241,7 +244,7 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
241
244
  const pluginTypes = new Set([...REQUIRED_PLUGIN_TYPES, ...undeployTypes])
242
245
 
243
246
  // compile to old format (.hdbcds) or new format (.hdbtable / .hdbview)
244
- const format = this.env.get("requires.db.deploy-format") || this.env.get("hana.deploy-format")
247
+ const format = this.getBuildOption(DEPLOY_FORMAT) || this.env.get("requires.db." + DEPLOY_FORMAT) || this.env.get("hana." + DEPLOY_FORMAT)
245
248
  if (!this.cds.compile.to[format]) {
246
249
  return Promise.reject(new Error(`Invalid deploy-format defined: ${format}`))
247
250
  }
@@ -4,12 +4,12 @@ const path = require('path')
4
4
  const BuildTaskHandlerEdmx = require('../buildTaskHandlerEdmx')
5
5
  const { isOldJavaStack, BuildError } = require('../../util')
6
6
 
7
- const { BUILD_OPTION_OUTPUT_MODE, ODATA_VERSION, ODATA_VERSION_V2, OUTPUT_MODE_RESULT_ONLY, FILE_EXT_CDS, SKIP_ASSERT_COMPILER_V2, CONTENT_LANGUAGE_BUNDLES, CONTENT_DEFAULT_CSN } = require('../../constants')
7
+ const { BUILD_OPTION_OUTPUT_MODE, ODATA_VERSION, ODATA_VERSION_V2, OUTPUT_MODE_RESULT_ONLY, FILE_EXT_CDS, SKIP_ASSERT_COMPILER_V2, CONTENT_LANGUAGE_BUNDLES, CONTENT_DEFAULT_CSN, DEFAULT_CSN_FILE_NAME } = require('../../constants')
8
8
  const { INFO } = require('../../buildTaskHandler')
9
9
 
10
10
  const DEFAULT_COMPILE_DEST_FOLDER = path.normalize('src/main/resources/edmx')
11
11
 
12
- class JavaCfModuleBuilder extends BuildTaskHandlerEdmx {
12
+ class JavaModuleBuilder extends BuildTaskHandlerEdmx {
13
13
  init() {
14
14
  super.init()
15
15
  this.task.options.compileDest = path.resolve(this.task.dest, this.task.options.compileDest || DEFAULT_COMPILE_DEST_FOLDER)
@@ -48,7 +48,10 @@ class JavaCfModuleBuilder extends BuildTaskHandlerEdmx {
48
48
 
49
49
  if (this.hasBuildOption(CONTENT_LANGUAGE_BUNDLES, true)) {
50
50
  // collect and write language bundles into single i18n.json file
51
- await this.collectLanguageBundles(model, this.task.dest)
51
+ const i18n = await this.collectLanguageBundles(model, this.task.dest)
52
+ if (i18n) {
53
+ this._result.languageBundles = i18n.bundles
54
+ }
52
55
  }
53
56
  if (!this.hasBuildOption(BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY)) {
54
57
  await this._copyNativeContent(src, dest)
@@ -62,7 +65,7 @@ class JavaCfModuleBuilder extends BuildTaskHandlerEdmx {
62
65
  return
63
66
  }
64
67
  this.logger._debug && this.logger.debug(`Deleting build target folder ${this.task.options.compileDest}`)
65
- await fs.promises.rm(this.task.options.compileDest, {force: true, recursive: true})
68
+ await fs.promises.rm(this.task.options.compileDest, { force: true, recursive: true })
66
69
  }
67
70
 
68
71
  async _copyNativeContent(src, dest) {
@@ -82,13 +85,20 @@ class JavaCfModuleBuilder extends BuildTaskHandlerEdmx {
82
85
  ...this._options4odata(),
83
86
  ...compileOptions
84
87
  })
85
-
88
+ const csnFile = path.join(csnDest, DEFAULT_CSN_FILE_NAME)
89
+ let csnModel
86
90
  // adding csn to build result containing @source and _where persisted properties
87
91
  if (this.hasBuildOption(CONTENT_DEFAULT_CSN, true)) { //default true or undefined
88
- await this.compileToJson(model, csnDest)
92
+ const csnStr = await this.compileToJson(model, csnFile)
93
+ csnModel = JSON.parse(csnStr)
94
+ csnModel.meta = model.meta
89
95
  } else {
90
- await this.compileToJson(m, csnDest)
96
+ const csnStr = await this.compileToJson(m, csnFile)
97
+ csnModel = JSON.parse(csnStr)
98
+ csnModel.meta = m.meta
91
99
  }
100
+ this._result.csn = csnModel
101
+
92
102
  return m
93
103
  }
94
104
 
@@ -98,4 +108,4 @@ class JavaCfModuleBuilder extends BuildTaskHandlerEdmx {
98
108
  return match && match[1] === 1
99
109
  }
100
110
  }
101
- module.exports = JavaCfModuleBuilder
111
+ module.exports = JavaModuleBuilder
@@ -47,7 +47,7 @@ class StreamlinedMtxBuilder {
47
47
  if (!model) {
48
48
  return
49
49
  }
50
- return new ResourcesTarProvider(this._handler).writeTar(dest, model)
50
+ return new ResourcesTarProvider(this._handler).createTar(dest, model)
51
51
  }
52
52
  async clean() {
53
53
  // staging build content is deleted by BuildTaskEngine
@@ -96,8 +96,11 @@ class ClassicMtxBuilder {
96
96
  }))
97
97
 
98
98
  // collect and write language bundles into single i18n.json file
99
- await this._handler.collectLanguageBundles(model, destSdc)
100
-
99
+ const i18n = await this._handler.collectLanguageBundles(model, destSdc)
100
+ if (i18n) {
101
+ this._handler._result.languageBundles = i18n.bundles
102
+ }
103
+
101
104
  // copy native hana content and templates
102
105
  await this._copyNativeContent(this._handler.task.src, destSdc, tenantDbPath)
103
106
  }
@@ -172,7 +175,7 @@ class ClassicMtxBuilder {
172
175
  function isValid(e, pattern, nsPattern, kind) {
173
176
  return e && e.name && (e.name === pattern || (nsPattern && e.name.startsWith(nsPattern))) && (!kind || e.kind === kind)
174
177
  }
175
-
178
+
176
179
  if (extensionAllowlist || entityWhitelist || serviceWhitelist) {
177
180
  const invalidEntries = new Set()
178
181
  const reflected = this._handler.cds.reflect(model)
@@ -1,52 +1,77 @@
1
- const fs = require('fs')
2
1
  const path = require('path')
3
2
  const BuildTaskHandlerInternal = require('../buildTaskHandlerInternal')
4
3
 
5
4
  const { BUILD_TASK_HANA } = require('../../constants')
6
- const { WARNING, INFO } = require('../../buildTaskHandler')
7
- const TAR_NAME = 'resources.tgz'
5
+ const { WARNING } = BuildTaskHandlerInternal
6
+ const DEFAULT_TAR_NAME = "resources.tgz"
8
7
 
9
8
  class ResourcesTarBuilder {
10
9
  constructor(handler) {
11
10
  this._handler = handler
12
11
  }
13
12
 
14
- async packTar(model) {
15
- let resourceRoot = await this._getHanaTenantDbDest()
16
- let files
17
- if (resourceRoot) {
18
- const hanaSrcFolder = path.join(resourceRoot, 'src')
19
- const undeployJson = path.join(resourceRoot, 'undeploy.json')
13
+ get handler() { return this._handler }
20
14
 
21
- // hana build task has already been executed
22
- files = BuildTaskHandlerInternal._find(resourceRoot, (res) => {
23
- if (res === undeployJson) {
24
- return true
25
- }
26
- if (res.startsWith(hanaSrcFolder)) {
27
- return true
28
- }
29
- return false
30
- })
31
- } else {
32
- resourceRoot = path.join(this._handler.buildOptions.root, this._handler.env.folders.db)
33
- const deploy = require('../../../../lib/deploy')
34
- files = Object.keys(await deploy.resources(model))
35
- }
36
- if (files.length > 0) {
37
- const { packTarArchive } = require('../../../../lib/utils/resources')
38
- return packTarArchive(files, resourceRoot, false)
39
- } else {
40
- this._handler.pushMessage("No deployment resources found - skip resource.tgz", WARNING)
15
+ async createTar(dest, model) {
16
+ const { root, files } = await this._getResources(model)
17
+ if (files.length === 0) {
18
+ // packTarArchive failes otherwise
19
+ this.handler.pushMessage("No deployment resources found - skip resources.tgz", WARNING)
20
+ return
41
21
  }
22
+ await this.writeTarFile(files, root, path.join(dest, DEFAULT_TAR_NAME))
42
23
  }
43
24
 
44
- async writeTar(dest, model) {
45
- const buffer = await this.packTar(model)
25
+ async writeTarFile(files, root, tarFile) {
26
+ const { packTarArchive } = require('../../../../lib/utils/resources')
27
+ const buffer = await packTarArchive(files, root)
46
28
  if (buffer) {
47
- await this._handler.write(buffer).to(path.join(dest, TAR_NAME))
29
+ await this.handler.write(buffer).to(tarFile)
30
+ }
31
+ }
32
+
33
+ async _getResources(model) {
34
+ // distinguish HANA and SQLITE deployment
35
+ let root = await this._getHanaTenantDbDest()
36
+ let files
37
+ if (root) {
38
+ files = this._getHanaResources(root)
39
+ } else {
40
+ root = path.join(this.handler.buildOptions.root, this.handler.env.folders.db)
41
+ files = await this._getSqliteResources(model)
48
42
  }
49
- await fs.promises.mkdir(dest, { recursive: true })
43
+ return { root, files }
44
+ }
45
+
46
+ /**
47
+ * Reads all resources for HANA deployment - 'db/src/**' and 'db/undeploy.json'.
48
+ * - CSV files, native HANA artefacts, generated HANA artefacts, undeploy.json
49
+ * See '../../../../lib.deploy'
50
+ * Note: the hana build task has already been executed
51
+ * @param {string} root
52
+ * @returns an array containing all resources
53
+ */
54
+ _getHanaResources(root) {
55
+ const hanaNativeFolders = [path.join(root, 'src'), path.join(root, 'cfg')]
56
+ const hanaNativeFiles = [path.join(root, 'undeploy.json'), path.join(root, '.hdiignore')]
57
+
58
+ return BuildTaskHandlerInternal._find(root, (res) => {
59
+ if (hanaNativeFolders.some(folder => res.startsWith(folder)) || hanaNativeFiles.some(file => res === file)) {
60
+ return true
61
+ }
62
+ return false
63
+ })
64
+ }
65
+
66
+ /**
67
+ * Reads all resources for Sqlite deployment - see '../../../../lib.deploy'
68
+ *
69
+ * @param {object} model
70
+ * @returns
71
+ */
72
+ async _getSqliteResources(model) {
73
+ const { resources } = this.handler.cds.deploy
74
+ return Object.keys(await resources(model))
50
75
  }
51
76
 
52
77
  /**
@@ -56,10 +81,10 @@ class ResourcesTarBuilder {
56
81
  * @returns {string} the src folder of the tenant db module
57
82
  */
58
83
  async _getHanaTenantDbDest() {
59
- const buildOptions = this._handler.buildOptions
84
+ const buildOptions = this.handler.buildOptions
60
85
  let hanaTask = buildOptions.tasks ? buildOptions.tasks.find(task => task.for === BUILD_TASK_HANA) : undefined
61
86
  if (!hanaTask) {
62
- this._handler.pushMessage(`Found SQLite database configuration. Ensure that productive cloud deployments requiring SAP HANA database are built with 'production' profile.`, INFO)
87
+ this.handler.pushMessage(`Found SQLite database configuration. Ensure that productive cloud deployments requiring SAP HANA database are built with 'production' profile.`, WARNING)
63
88
  return null
64
89
  }
65
90
  return hanaTask.dest
@@ -0,0 +1,57 @@
1
+ const path = require('path')
2
+ const BuildTaskHandlerInternal = require('../buildTaskHandlerInternal')
3
+ const { FOLDER_GEN } = require('../../constants')
4
+ const ResourcesTarBuilder = require('../mtx/resourcesTarBuilder')
5
+
6
+ class MtxExtensionModuleBuilder extends BuildTaskHandlerInternal {
7
+ init() {
8
+ super.init()
9
+ if (this.buildOptions.root === this.buildOptions.target) {
10
+ this.task.dest = path.join(this.task.dest, FOLDER_GEN)
11
+ }
12
+ }
13
+
14
+ async build() {
15
+ const model = await this.model()
16
+ if (!model) {
17
+ return
18
+ }
19
+ const allFiles = []
20
+ const destExt = path.join(this.task.dest, 'ext')
21
+
22
+ const packageJson = path.join(destExt, 'package.json')
23
+ await this.copy(path.join(this.task.src, 'package.json')).to(packageJson)
24
+ allFiles.push(packageJson)
25
+
26
+ // extension CSN using parsed format
27
+ const options = { ...this.options(), flavor: 'parsed' }
28
+ const extCsn = await this.cds.load(this.resolveModel(), options)
29
+ if (extCsn.requires) {
30
+ extCsn.requires.length = 0
31
+ }
32
+ const csnFile = path.join(destExt, 'extension.csn')
33
+ await this.compileToJson(extCsn, csnFile)
34
+ allFiles.push(csnFile)
35
+
36
+ const i18n = await this.collectLanguageBundles(extCsn, destExt)
37
+ if (i18n) {
38
+ allFiles.push(i18n.file)
39
+ }
40
+
41
+ const files = Object.keys(await this.cds.deploy.resources(model))
42
+ if (files.length > 0) {
43
+ const dataDest = path.join(destExt, 'data')
44
+ await Promise.all(
45
+ files
46
+ .filter(file => /\.csv$/.test(file))
47
+ .map(csv => {
48
+ const csvFile = path.join(dataDest, path.basename(csv))
49
+ allFiles.push(csvFile)
50
+ return this.copy(csv).to(csvFile)
51
+ })
52
+ )
53
+ }
54
+ await new ResourcesTarBuilder(this).writeTarFile(allFiles, destExt, path.join(this.task.dest, 'extension.tgz'))
55
+ }
56
+ }
57
+ module.exports = MtxExtensionModuleBuilder
@@ -1,18 +1,17 @@
1
- /* eslint-disable no-empty */
2
1
  const path = require('path')
3
- const { FOLDER_GEN } = require('../../constants')
4
- const BuildTaskHandlerInternal = require('../buildTaskHandlerInternal')
2
+ const fs = require('fs')
3
+ const { FOLDER_GEN, DEFAULT_CSN_FILE_NAME } = require('../../constants')
5
4
  const NodeCfModuleBuilder = require('../nodejs')
6
5
  const ResourcesTarProvider = require('../mtx/resourcesTarBuilder')
7
- const { INFO } = require('../buildTaskHandlerInternal')
8
- const { relativePaths } = require('@sap/cds/bin/build/util')
6
+ const { INFO, ERROR } = NodeCfModuleBuilder
7
+ const { relativePaths, BuildError } = require('../../util')
9
8
 
10
9
  const DEFAULT_MAIN_FOLDER = "_main"
11
10
 
12
11
  class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
13
12
  get priority() {
14
13
  // should be scheduled after 'hana' build tasks are finished
15
- return BuildTaskHandlerInternal.PRIORITY_MIN_VALUE
14
+ return NodeCfModuleBuilder.PRIORITY_MIN_VALUE
16
15
  }
17
16
  init() {
18
17
  super.init()
@@ -25,7 +24,7 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
25
24
  * Builds the mtx sidecar app consisting of:
26
25
  * - nodejs app model defined by the required sidecar services
27
26
  * - main app model defined by the build task's model options including feature models and resources TAR
28
- *
27
+ *
29
28
  * build.target=".": 'dest' -> 'model-provider/gen'
30
29
  * build.target="gen": 'dest' -> 'gen/model-provider'
31
30
  */
@@ -46,7 +45,7 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
46
45
  const destSidecar = this.task.dest
47
46
  const destSidecarSrc = path.join(destSidecar, this.env.folders.srv)
48
47
  const model = this._compileSidecarSync(sidecarEnv)
49
- await this.compileToJson(model, destSidecarSrc)
48
+ await this.compileToJson(model, path.join(destSidecarSrc, DEFAULT_CSN_FILE_NAME))
50
49
  await this.collectLanguageBundles(model, destSidecarSrc)
51
50
  await this._copyProjectRootContent(this.task.src, destSidecar)
52
51
  }
@@ -56,11 +55,12 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
56
55
  * @param {object} sidecarEnv cds env based on the sidecar dir
57
56
  */
58
57
  async _buildMainApp(sidecarEnv) {
59
- let main = sidecarEnv.requires['cds.xt.ModelProviderService']?.root
60
- if (!main) {
61
- throw new Error("Invalid sidecar configuration - {\"model-provider\": \"in-sidecar\"} configuration missing")
58
+ if (sidecarEnv.requires['cds.xt.ModelProviderService']?.kind !== 'in-sidecar') {
59
+ throw new Error("Invalid MTX sidecar configuration - \"cds.xt.ModelProviderService\": \"in-sidecar\" missing")
62
60
  }
61
+ let main = sidecarEnv.requires['cds.xt.ModelProviderService']?.root
63
62
  const profiles = this.env.get("profiles") || []
63
+
64
64
  if (!profiles.includes("production") && !profiles.includes("prod")) {
65
65
  main = DEFAULT_MAIN_FOLDER
66
66
  // root should represent the production use case and not development
@@ -78,7 +78,10 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
78
78
 
79
79
  // create resources TAR
80
80
  // resources are determined based on available database build task, SQLite as fallback
81
- await new ResourcesTarProvider(this).writeTar(destMain, csn)
81
+ await new ResourcesTarProvider(this).createTar(destMain, csn)
82
+
83
+ // copy package.json and .cdsrc.json from project root
84
+ await this._copyMainConfigFiles(this.cds.root, destMain)
82
85
  }
83
86
 
84
87
  /**
@@ -91,20 +94,45 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
91
94
  try {
92
95
  this.cds.root = this.task.src
93
96
  this.cds.env = sidecarEnv
94
- let modelPaths = this.cds.resolve('*', false)
95
- modelPaths = this.cds.resolve(modelPaths)
96
- if (!modelPaths || modelPaths.length === 0) {
97
- throw new Error("No model found for MTX sidecar app")
97
+ const modelPaths = this.cds.resolve('*', false)
98
+ const modelFilePaths = this.cds.resolve(modelPaths)
99
+
100
+ if (!modelFilePaths || modelFilePaths.length === 0) {
101
+ throw new BuildError("No model found for MTX sidecar app")
98
102
  }
99
- this._logger._debug && this._logger.debug(`sidecar model: ${relativePaths(this.buildOptions.root, modelPaths).join(", ")}`)
103
+ // candidate for strict mode support
104
+ // entries for @sap/** model paths are missing if the module hasn't been installed, e.g. @sap/cds-mtxs
105
+ const missingModels = modelPaths.filter(p => {
106
+ if (p.startsWith('@sap/')) {
107
+ const files = this.cds.resolve(p)
108
+ return !files || files.length === 0
109
+ }
110
+ })
111
+ if (missingModels.length > 0) {
112
+ this.pushMessage(`Some model paths could not be resolved. Make sure the npm modules have been installed: ${missingModels.join(', ')}`, ERROR)
113
+ }
114
+ this._logger._debug && this._logger.debug(`sidecar model: ${relativePaths(this.buildOptions.root, modelFilePaths).join(", ")}`)
100
115
 
101
116
  // synchronous compilation
102
- return this.cds.load(modelPaths, { sync: true, ...this.options() })
117
+ return this.cds.load(modelFilePaths, { sync: true, ...this.options() })
103
118
  } finally {
104
119
  // restore project scope
105
120
  this.cds.root = this.buildOptions.root
106
121
  this.cds.env = env
107
122
  }
108
123
  }
124
+
125
+ async _copyMainConfigFiles(src, dest) {
126
+ const packageJson = path.join(src, 'package.json')
127
+ const cdsrcJson = path.join(src, '.cdsrc.json')
128
+ const promises = []
129
+ if (fs.existsSync(packageJson)) {
130
+ promises.push(this.copy(packageJson).to(path.join(dest, 'package.json')))
131
+ }
132
+ if (fs.existsSync(cdsrcJson)) {
133
+ promises.push(this.copy(cdsrcJson).to(path.join(dest, '.cdsrc.json')))
134
+ }
135
+ return promises
136
+ }
109
137
  }
110
138
  module.exports = MtxSidecarModuleBuilder