@sap/cds 6.3.1 → 6.4.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 (114) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/apis/cds.d.ts +1 -1
  3. package/apis/core.d.ts +118 -90
  4. package/apis/cqn.d.ts +11 -2
  5. package/apis/internal/inference.d.ts +7 -2
  6. package/apis/ql.d.ts +45 -11
  7. package/apis/serve.d.ts +8 -1
  8. package/apis/services.d.ts +303 -305
  9. package/bin/build/buildTaskEngine.js +28 -36
  10. package/bin/build/buildTaskFactory.js +32 -81
  11. package/bin/build/buildTaskHandler.js +3 -2
  12. package/bin/build/buildTaskProvider.js +2 -2
  13. package/bin/build/buildTaskProviderFactory.js +5 -14
  14. package/bin/build/constants.js +0 -1
  15. package/bin/build/provider/buildTaskHandlerEdmx.js +7 -6
  16. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +6 -5
  17. package/bin/build/provider/buildTaskHandlerInternal.js +9 -30
  18. package/bin/build/provider/buildTaskProviderInternal.js +70 -58
  19. package/bin/build/provider/fiori/index.js +6 -5
  20. package/bin/build/provider/hana/2migration.js +20 -3
  21. package/bin/build/provider/hana/2tabledata.js +1 -0
  22. package/bin/build/provider/hana/index.js +40 -17
  23. package/bin/build/provider/java/index.js +10 -10
  24. package/bin/build/provider/mtx/index.js +25 -16
  25. package/bin/build/provider/mtx/resourcesTarBuilder.js +22 -27
  26. package/bin/build/provider/mtx-extension/index.js +3 -2
  27. package/bin/build/provider/mtx-sidecar/index.js +16 -15
  28. package/bin/build/provider/nodejs/index.js +14 -56
  29. package/bin/build/util.js +56 -16
  30. package/bin/deploy/to-hana/cfUtil.js +4 -1
  31. package/bin/deploy/to-hana/gitUtil.js +1 -1
  32. package/bin/deploy/to-hana/hana.js +45 -38
  33. package/bin/deploy/to-hana/hdiDeployUtil.js +8 -9
  34. package/bin/deploy/to-hana/mtaUtil.js +13 -14
  35. package/bin/mtx/in-cds.js +3 -1
  36. package/bin/serve.js +1 -1
  37. package/bin/version.js +2 -1
  38. package/lib/compile/cds-compile.js +1 -0
  39. package/lib/compile/cdsc.js +1 -0
  40. package/lib/compile/etc/_localized.js +2 -2
  41. package/lib/compile/for/lean_drafts.js +83 -0
  42. package/lib/compile/for/nodejs.js +1 -0
  43. package/lib/compile/minify.js +2 -1
  44. package/lib/compile/parse.js +2 -1
  45. package/lib/compile/to/gql.js +1 -1
  46. package/lib/compile/to/sql.js +11 -1
  47. package/lib/core/entities.js +1 -1
  48. package/lib/core/index.js +8 -9
  49. package/lib/core/infer.js +1 -0
  50. package/lib/dbs/cds-deploy.js +97 -41
  51. package/lib/env/cds-env.js +9 -10
  52. package/lib/env/cds-requires.js +8 -2
  53. package/lib/env/defaults.js +0 -4
  54. package/lib/env/schemas/cds-rc.json +38 -0
  55. package/lib/ql/SELECT.js +10 -4
  56. package/lib/srv/bindings.js +1 -1
  57. package/lib/srv/factory.js +1 -1
  58. package/lib/srv/protocols/index.js +3 -1
  59. package/lib/srv/srv-methods.js +1 -1
  60. package/lib/utils/cds-utils.js +11 -0
  61. package/lib/utils/inflect.js +13 -12
  62. package/lib/utils/tar.js +53 -10
  63. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +2 -2
  64. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
  65. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
  66. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -1
  67. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -15
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -1
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -1
  70. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/UriSyntaxError.js +1 -1
  71. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +6 -1
  72. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js +1 -1
  73. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -12
  74. package/libx/_runtime/cds-services/adapter/odata-v4/utils/oDataConfiguration.js +1 -7
  75. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +4 -0
  76. package/libx/_runtime/cds-services/services/Service.js +23 -1
  77. package/libx/_runtime/cds-services/util/assert.js +0 -41
  78. package/libx/_runtime/common/composition/data.js +5 -1
  79. package/libx/_runtime/common/generic/auth/utils.js +3 -3
  80. package/libx/_runtime/common/generic/input.js +4 -24
  81. package/libx/_runtime/common/generic/paging.js +3 -3
  82. package/libx/_runtime/common/utils/csn.js +21 -15
  83. package/libx/_runtime/common/utils/draft.js +2 -1
  84. package/libx/_runtime/common/utils/resolveView.js +25 -4
  85. package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -1
  86. package/libx/_runtime/common/utils/rowUUIDGenerator.js +21 -0
  87. package/libx/_runtime/common/utils/templateProcessor.js +12 -15
  88. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +23 -0
  89. package/libx/_runtime/db/expand/expandCQNToJoin.js +29 -12
  90. package/libx/_runtime/db/generic/input.js +7 -13
  91. package/libx/_runtime/db/sql-builder/UpsertBuilder.js +47 -0
  92. package/libx/_runtime/db/sql-builder/index.js +2 -0
  93. package/libx/_runtime/db/sql-builder/sqlFactory.js +9 -0
  94. package/libx/_runtime/db/utils/columns.js +4 -2
  95. package/libx/_runtime/fiori/generic/read.js +1 -12
  96. package/libx/_runtime/fiori/lean-draft.js +657 -0
  97. package/libx/_runtime/fiori/utils/handler.js +1 -1
  98. package/libx/_runtime/hana/pool.js +16 -1
  99. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -1
  100. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -1
  101. package/libx/_runtime/messaging/enterprise-messaging.js +2 -3
  102. package/libx/_runtime/messaging/outbox/utils.js +109 -70
  103. package/libx/_runtime/messaging/service.js +16 -7
  104. package/libx/_runtime/remote/Service.js +15 -2
  105. package/libx/_runtime/remote/utils/client.js +41 -11
  106. package/libx/_runtime/sqlite/Service.js +3 -0
  107. package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +56 -0
  108. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +59 -0
  109. package/libx/_runtime/sqlite/customBuilder/index.js +5 -0
  110. package/libx/_runtime/sqlite/execute.js +1 -1
  111. package/libx/_runtime/types/api.js +2 -2
  112. package/libx/rest/RestAdapter.js +15 -13
  113. package/package.json +1 -1
  114. package/server.js +1 -0
@@ -1,6 +1,6 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
-
3
+ const cds = require('../../cds')
4
4
  const BuildTaskHandlerEdmx = require('../buildTaskHandlerEdmx')
5
5
  const { isOldJavaStack, BuildError } = require('../../util')
6
6
 
@@ -19,22 +19,22 @@ class JavaModuleBuilder extends BuildTaskHandlerEdmx {
19
19
  const { src, dest } = this.task
20
20
 
21
21
  const odataOptions = {
22
- version: this.env.odata?.version
22
+ version: cds.env.odata?.version
23
23
  }
24
24
 
25
25
  if (await isOldJavaStack([src, this.buildOptions.root])) {
26
- if (!this._isCompilerV1() && !this.env.build?.[SKIP_ASSERT_COMPILER_V2]) {
26
+ if (!this._isCompilerV1() && !cds.env.build?.[SKIP_ASSERT_COMPILER_V2]) {
27
27
  throw new BuildError('CDS compiler version 2 does no longer support the classic CAP Java runtime. It is recommended to migrate to the current CAP Java runtime SDK. See https://cap.cloud.sap/docs/java/migration for more.')
28
28
  }
29
29
  // default is now v4 and not v2 anymore, so warn and overwrite with v2 if using default
30
- if (!this.env.for('cds', this.buildOptions.root, false).odata?.version) {
30
+ if (!cds.env.for('cds', this.buildOptions.root, false).odata?.version) {
31
31
  odataOptions.version = ODATA_VERSION_V2
32
32
  this.pushMessage('Forcing OData v2 for building though the default is v4. Make sure to define OData v2 in cds configuration.', INFO)
33
33
  }
34
34
 
35
- // 'sql_mapping' and 'cds.persinstence.name' annotations are required by old java stack
36
- if (this.env.sql.names !== 'plain') {
37
- odataOptions.sql_mapping = this.env.sql.names
35
+ // 'sql_mapping' and 'cds.persistence.name' annotations are required by old java stack
36
+ if (cds.env.sql.names !== 'plain') {
37
+ odataOptions.sql_mapping = cds.env.sql.names
38
38
  }
39
39
  }
40
40
 
@@ -48,7 +48,7 @@ class JavaModuleBuilder 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
- const i18nFolder = this.env.i18n.folders?.[0] || 'i18n'
51
+ const i18nFolder = cds.env.i18n.folders?.[0] || 'i18n'
52
52
  const i18n = await this.collectLanguageBundles(model, path.join(this.task.dest, i18nFolder))
53
53
  if (i18n) {
54
54
  this._result.languageBundles = i18n.bundles
@@ -81,7 +81,7 @@ class JavaModuleBuilder extends BuildTaskHandlerEdmx {
81
81
 
82
82
  async _compileForOdata(model, csnDest, compileOptions) {
83
83
  // csn for service providers
84
- const m = this.cds.compile.for.java(model, {
84
+ const m = cds.compile.for.java(model, {
85
85
  ...this._options4odata(),
86
86
  ...compileOptions
87
87
  })
@@ -103,7 +103,7 @@ class JavaModuleBuilder extends BuildTaskHandlerEdmx {
103
103
  }
104
104
 
105
105
  _isCompilerV1() {
106
- const version = this.cds.compiler.version()
106
+ const version = cds.compiler.version()
107
107
  const match = version.match(/(\d+)\.?(\d*)\.?(\d*)/)
108
108
  return match && match[1] === 1
109
109
  }
@@ -1,9 +1,10 @@
1
1
  /* eslint-disable no-empty */
2
2
  const fs = require('fs')
3
3
  const path = require('path')
4
+ const cds = require('../../cds')
4
5
  const BuildTaskProviderInternal = require("../buildTaskProviderInternal")
5
6
  const BuildTaskHandlerEdmx = require('../buildTaskHandlerEdmx')
6
- const { isStreamlinedMtx } = require('../../util')
7
+ const { isStreamlinedMtx, hasJavaNature } = require('../../util')
7
8
  const ResourcesTarProvider = require('./resourcesTarBuilder')
8
9
 
9
10
  const { BUILD_TASK_HANA, FOLDER_GEN } = require('../../constants')
@@ -19,10 +20,18 @@ class MtxModuleBuilder extends BuildTaskHandlerEdmx {
19
20
  }
20
21
  init() {
21
22
  super.init()
22
- if (isStreamlinedMtx(this.cds)) {
23
- this._delegate = new StreamlinedMtxBuilder(this)
24
- } else {
23
+
24
+ if (hasJavaNature([this.buildOptions.root, this.task.src, cds.env.folders.srv])) {
25
+ // Java projects having this build task configured are using Classic MTX
26
+ // Streamlined MTX based Java projects have to configure the 'mtx-sidecar' build task instead.
25
27
  this._delegate = new ClassicMtxBuilder(this)
28
+ } else {
29
+ // Nodejs projects
30
+ if (isStreamlinedMtx()) {
31
+ this._delegate = new StreamlinedMtxBuilder(this)
32
+ } else {
33
+ this._delegate = new ClassicMtxBuilder(this)
34
+ }
26
35
  }
27
36
  }
28
37
 
@@ -42,7 +51,7 @@ class StreamlinedMtxBuilder {
42
51
  async build() {
43
52
  // Non-sidecar scenario: model-provider is implemented by the Nodejs app itself (default for Nodejs).
44
53
  // Only create resources TAR, compiled CSN(s) are created by the Nodejs app build task.
45
- const dest = this._handler.env.build?.target !== "." ? this._handler.task.dest : path.join(this._handler.task.dest, FOLDER_GEN)
54
+ const dest = cds.env.build?.target !== "." ? this._handler.task.dest : path.join(this._handler.task.dest, FOLDER_GEN)
46
55
  const model = await this._handler.model()
47
56
  if (!model) {
48
57
  return
@@ -64,7 +73,7 @@ class ClassicMtxBuilder {
64
73
  }
65
74
 
66
75
  async build() {
67
- if (this._handler.env.requires.toggles === true) {
76
+ if (cds.env.requires.toggles === true) {
68
77
  throw new Error("Feature toggles are only supported by Streamlined MTX")
69
78
  }
70
79
 
@@ -96,7 +105,7 @@ class ClassicMtxBuilder {
96
105
  }))
97
106
 
98
107
  // collect and write language bundles into single i18n.json file
99
- const i18nFolder = this._handler.env.i18n.folders?.[0] || 'i18n'
108
+ const i18nFolder = cds.env.i18n.folders?.[0] || 'i18n'
100
109
  const i18n = await this._handler.collectLanguageBundles(model, path.join(destSdc, i18nFolder))
101
110
  if (i18n) {
102
111
  this._handler._result.languageBundles = i18n.bundles
@@ -163,13 +172,13 @@ class ClassicMtxBuilder {
163
172
  * @param {Object} model - the reflected csn for this SaaS application
164
173
  */
165
174
  _validateExtensionAllowLists(model, tenantDbPath) {
166
- let entityWhitelist = this._handler.env.mtx?.["entity-whitelist"]
167
- let serviceWhitelist = this._handler.env.mtx?.["service-whitelist"]
168
- let extensionAllowlist = this._handler.env.mtx?.["extension-allowlist"]
175
+ let entityWhitelist = cds.env.mtx?.["entity-whitelist"]
176
+ let serviceWhitelist = cds.env.mtx?.["service-whitelist"]
177
+ let extensionAllowlist = cds.env.mtx?.["extension-allowlist"]
169
178
 
170
179
  // for java projects mtx configuration is part of sidecar config
171
180
  if (!entityWhitelist && !serviceWhitelist && !extensionAllowlist && tenantDbPath) {
172
- const dbEnv = this._handler.env.for("cds", tenantDbPath)
181
+ const dbEnv = cds.env.for("cds", tenantDbPath)
173
182
  entityWhitelist = dbEnv.mtx?.["entity-whitelist"]
174
183
  serviceWhitelist = dbEnv.mtx?.["service-whitelist"]
175
184
  extensionAllowlist = dbEnv.mtx?.["extension-allowlist"]
@@ -181,7 +190,7 @@ class ClassicMtxBuilder {
181
190
 
182
191
  if (extensionAllowlist || entityWhitelist || serviceWhitelist) {
183
192
  const invalidEntries = new Set()
184
- const reflected = this._handler.cds.reflect(model)
193
+ const reflected = cds.reflect(model)
185
194
 
186
195
  if (Array.isArray(extensionAllowlist)) {
187
196
  extensionAllowlist.forEach(allowListEntry => {
@@ -234,19 +243,19 @@ class ClassicMtxBuilder {
234
243
  // ensure that the hana task is contained even if this mtx task has been executed solely using "cds build --for mtx"
235
244
  if (!tasks.find(task => task.for === BUILD_TASK_HANA)) {
236
245
  //mtx task might have been executed as separate task
237
- tasks = this._handler.env.build?.tasks || []
246
+ tasks = cds.env.build?.tasks || []
238
247
  if (tasks.length === 0) {
239
- const provider = new BuildTaskProviderInternal(this._handler.cds, this._handler.logger)
248
+ const provider = new BuildTaskProviderInternal(this._handler.logger)
240
249
  await provider.lookupTasks(tasks, this._handler.buildOptions)
241
250
  }
242
251
  }
243
252
  // the SaaS app might use a tenant aware db as well as a shared db deployed using static hdi-deployer
244
253
  // pick the hana build task referring to the tenant aware db - the src path has to be contained in this build task's model options
245
- const hanaDbPaths = tasks.filter(task => task.for === BUILD_TASK_HANA).map(hanaTask => path.resolve(this._handler.buildOptions.root, hanaTask.src || this._handler.env.folders.db))
254
+ const hanaDbPaths = tasks.filter(task => task.for === BUILD_TASK_HANA).map(hanaTask => path.resolve(this._handler.buildOptions.root, hanaTask.src || cds.env.folders.db))
246
255
  let tenantDbPath = hanaDbPaths.find(hanaDbPath => hanaDbPaths.length === 1 || modelPaths.some(modelPath => path.dirname(modelPath) === hanaDbPath))
247
256
 
248
257
  if (!tenantDbPath) {
249
- tenantDbPath = path.join(this._handler.buildOptions.root, this._handler.env.folders.db)
258
+ tenantDbPath = path.join(this._handler.buildOptions.root, cds.env.folders.db)
250
259
  if (hanaDbPaths.length === 0) {
251
260
  this._handler.pushMessage(`no 'hana' build task found, use default location '${tenantDbPath}'`, WARNING)
252
261
  } else {
@@ -1,7 +1,9 @@
1
1
  const path = require('path')
2
+ const cds = require('../../cds')
2
3
  const BuildTaskHandlerInternal = require('../buildTaskHandlerInternal')
3
4
 
4
5
  const { BUILD_TASK_HANA } = require('../../constants')
6
+ const { fs } = require('../../../../lib/utils/cds-utils')
5
7
  const { WARNING } = BuildTaskHandlerInternal
6
8
  const DEFAULT_TAR_NAME = "resources.tgz"
7
9
 
@@ -13,52 +15,51 @@ class ResourcesTarBuilder {
13
15
  get handler() { return this._handler }
14
16
 
15
17
  async createTar(dest, model) {
16
- const { root, files } = await this._getResources(model)
17
- if (files.length === 0) {
18
+ const { root, resources } = await this._getResources(model)
19
+ if (resources.length === 0) {
18
20
  // packTarArchive fails otherwise
19
21
  this.handler.pushMessage("No deployment resources found - skip resources.tgz", WARNING)
20
22
  return
21
23
  }
22
- await this.writeTarFile(files, root, path.join(dest, DEFAULT_TAR_NAME))
24
+ await this.writeTarFile(resources, root, path.join(dest, DEFAULT_TAR_NAME))
23
25
  }
24
26
 
25
- async writeTarFile(files, root, tarFile) {
27
+ async writeTarFile(resources, root, tarFile) {
26
28
  const { tar } = require('../../../../lib').utils
27
- await tar.czfd (tarFile, root, files) // REVISIT: tar.czfd was created for this case only -> it ensures the target's dir exists
29
+ await tar.czfd(tarFile, root, resources) // REVISIT: tar.czfd was created for this case only -> it ensures the target's dir exists
28
30
  this.handler.pushFile(tarFile)
29
31
  }
30
32
 
31
33
  async _getResources(model) {
32
34
  // distinguish HANA and SQLITE deployment
33
35
  let root = await this._getHanaTenantDbDest()
34
- let files
36
+ let resources
35
37
  if (root) {
36
- files = this._getHanaResources(root)
38
+ resources = this._getHanaResources(root)
37
39
  } else {
38
- root = path.join(this.handler.buildOptions.root, this.handler.env.folders.db)
39
- files = await this._getSqliteResources(model)
40
+ root = path.join(this.handler.buildOptions.root, cds.env.folders.db)
41
+ resources = await this._getSqliteResources(model)
40
42
  }
41
- return { root, files }
43
+ return { root, resources }
42
44
  }
43
45
 
44
46
  /**
45
- * Reads all resources for HANA deployment - 'db/src/**' and 'db/undeploy.json'.
46
- * - CSV files, native HANA artifacts, generated HANA artifacts, undeploy.json
47
+ * Reads a list of files and folders required for HANA deployment.
48
+ * This is a subset of all files contained in the directory.
49
+ * - 'db/src', 'db/cfg', 'db/undeploy.json'...
47
50
  * See '../../../../lib.deploy'
48
51
  * Note: the hana build task has already been executed
49
52
  * @param {string} root
50
53
  * @returns an array containing all resources
51
54
  */
52
55
  _getHanaResources(root) {
53
- const hanaNativeFolders = [path.join(root, 'src'), path.join(root, 'cfg')]
54
- const hanaNativeFiles = [path.join(root, 'undeploy.json'), path.join(root, '.hdiignore')]
55
-
56
- return BuildTaskHandlerInternal._find(root, (res) => {
57
- if (hanaNativeFolders.some(folder => res.startsWith(folder)) || hanaNativeFiles.some(file => res === file)) {
58
- return true
56
+ return ['src', 'cfg', 'undeploy.json', '.hdiignore'].reduce((acc, res) => {
57
+ const resPath = path.join(root, res)
58
+ if (fs.existsSync(resPath)) {
59
+ acc.push(resPath)
59
60
  }
60
- return false
61
- })
61
+ return acc
62
+ }, [])
62
63
  }
63
64
 
64
65
  /**
@@ -68,16 +69,10 @@ class ResourcesTarBuilder {
68
69
  * @returns
69
70
  */
70
71
  async _getSqliteResources(model) {
71
- const { resources } = this.handler.cds.deploy
72
+ const { resources } = cds.deploy
72
73
  return Object.keys(await resources(model))
73
74
  }
74
75
 
75
- /**
76
- * Returns the build tasks of this project - either user defined or calculated by BuildTaskFactory.
77
- * A build task of type 'hana' is enforced in order to copy existing native hana artifacts later on.
78
- *
79
- * @returns {string} the src folder of the tenant db module
80
- */
81
76
  async _getHanaTenantDbDest() {
82
77
  const buildOptions = this.handler.buildOptions
83
78
  let hanaTask = buildOptions.tasks ? buildOptions.tasks.find(task => task.for === BUILD_TASK_HANA) : undefined
@@ -1,4 +1,5 @@
1
1
  const path = require('path')
2
+ const cds = require('../../cds')
2
3
  const BuildTaskHandlerInternal = require('../buildTaskHandlerInternal')
3
4
  const { FOLDER_GEN } = require('../../constants')
4
5
  const ResourcesTarBuilder = require('../mtx/resourcesTarBuilder')
@@ -25,7 +26,7 @@ class MtxExtensionModuleBuilder extends BuildTaskHandlerInternal {
25
26
 
26
27
  // extension CSN using parsed format
27
28
  const options = { ...this.options(), flavor: 'parsed' }
28
- const extCsn = await this.cds.load(this.resolveModel(), options)
29
+ const extCsn = await cds.load(this.resolveModel(), options)
29
30
  if (extCsn.requires) {
30
31
  extCsn.requires.length = 0
31
32
  }
@@ -39,7 +40,7 @@ class MtxExtensionModuleBuilder extends BuildTaskHandlerInternal {
39
40
  allFiles.push(i18n.file)
40
41
  }
41
42
 
42
- const files = Object.keys(await this.cds.deploy.resources(model))
43
+ const files = Object.keys(await cds.deploy.resources(model))
43
44
  if (files.length > 0) {
44
45
  const dataDest = path.join(destExt, 'data')
45
46
  await Promise.all(
@@ -1,4 +1,5 @@
1
1
  const path = require('path')
2
+ const cds = require('../../cds')
2
3
  const fs = require('fs')
3
4
  const { FOLDER_GEN, DEFAULT_CSN_FILE_NAME } = require('../../constants')
4
5
  const NodeCfModuleBuilder = require('../nodejs')
@@ -30,7 +31,7 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
30
31
  */
31
32
  async build() {
32
33
  // nodejs app parts have to be built using sidecar env
33
- const sidecarEnv = this.env.for("cds", this.task.src)
34
+ const sidecarEnv = cds.env.for("cds", this.task.src)
34
35
  // build main application
35
36
  await this._buildMainApp(sidecarEnv)
36
37
  // build node application
@@ -43,8 +44,8 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
43
44
  */
44
45
  async _buildNodeApp(sidecarEnv) {
45
46
  const destSidecar = this.task.dest
46
- const destSidecarSrc = path.join(destSidecar, this.env.folders.srv)
47
- const i18nFolder = this.env.i18n.folders?.[0] || 'i18n'
47
+ const destSidecarSrc = path.join(destSidecar, cds.env.folders.srv)
48
+ const i18nFolder = cds.env.i18n.folders?.[0] || 'i18n'
48
49
  const model = this._compileSidecarSync(sidecarEnv)
49
50
  await this.compileToJson(model, path.join(destSidecarSrc, DEFAULT_CSN_FILE_NAME))
50
51
 
@@ -61,7 +62,7 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
61
62
  throw new BuildError("CDS build failed", "Invalid MTX sidecar configuration - \"cds.xt.ModelProviderService\": \"in-sidecar\" missing.")
62
63
  }
63
64
  let main = sidecarEnv.requires['cds.xt.ModelProviderService']?.root
64
- const profiles = this.env.profiles || []
65
+ const profiles = cds.env.profiles || []
65
66
 
66
67
  if (!profiles.includes("production") && !profiles.includes("prod")) {
67
68
  main = DEFAULT_MAIN_FOLDER
@@ -70,7 +71,7 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
70
71
  }
71
72
  const destRoot = this.task.dest
72
73
  const destMain = path.join(destRoot, main)
73
- const destMainSrv = path.join(destMain, this.env.folders.srv)
74
+ const destMainSrv = path.join(destMain, cds.env.folders.srv)
74
75
  const csn = await this.model()
75
76
  if (!csn) {
76
77
  return
@@ -83,7 +84,7 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
83
84
  await new ResourcesTarProvider(this).createTar(destMain, csn)
84
85
 
85
86
  // copy package.json and .cdsrc.json from project root
86
- await this._copyMainConfigFiles(this.cds.root, destMain)
87
+ await this._copyMainConfigFiles(cds.root, destMain)
87
88
  }
88
89
 
89
90
  /**
@@ -92,12 +93,12 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
92
93
  * @returns the compiled mtx sidecar CSN
93
94
  */
94
95
  _compileSidecarSync(sidecarEnv) {
95
- const env = this.env
96
+ const env = cds.env
96
97
  try {
97
- this.cds.root = this.task.src
98
- this.cds.env = sidecarEnv
99
- const modelPaths = this.cds.resolve('*', false)
100
- const modelFilePaths = this.cds.resolve(modelPaths)
98
+ cds.root = this.task.src
99
+ cds.env = sidecarEnv
100
+ const modelPaths = cds.resolve('*', false)
101
+ const modelFilePaths = cds.resolve(modelPaths)
101
102
 
102
103
  if (!modelFilePaths || modelFilePaths.length === 0) {
103
104
  throw new BuildError("No CDS service models found in MTX sidecar. Make sure required npm modules are installed.")
@@ -105,18 +106,18 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
105
106
  this._logger._debug && this._logger.debug(`sidecar model: ${relativePaths(this.buildOptions.root, modelFilePaths).join(", ")}`)
106
107
 
107
108
  // check whether all models belonging to the @sap namespace can be resolved
108
- const unresolved = resolveRequiredSapModels(this.cds, modelPaths)
109
+ const unresolved = resolveRequiredSapModels(modelPaths)
109
110
  if (unresolved.length > 0) {
110
111
  // log error, but don't fail
111
112
  this.pushMessage(`CDS service models [${unresolved.join(', ')}] required by MTX sidecar cannot be resolved. Make sure to install the missing npm modules.`, ERROR)
112
113
  }
113
114
 
114
115
  // synchronous compilation
115
- return this.cds.load(modelFilePaths, { sync: true, ...this.options() })
116
+ return cds.load(modelFilePaths, { sync: true, ...this.options() })
116
117
  } finally {
117
118
  // restore project scope
118
- this.cds.root = this.buildOptions.root
119
- this.cds.env = env
119
+ cds.root = this.buildOptions.root
120
+ cds.env = env
120
121
  }
121
122
  }
122
123
 
@@ -1,10 +1,12 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
+ const cds = require('../../cds')
3
4
  const BuildTaskHandlerEdmx = require('../buildTaskHandlerEdmx')
4
5
  const { BuildError } = require('../../util')
5
6
  const { BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY, ODATA_VERSION_V2, FOLDER_GEN, BUILD_NODEJS_EDMX_GENERAION, EDMX_GENERATION,
6
- SKIP_PACKAGE_JSON_GENERATION, CONTENT_EDMX, CONTENT_PACKAGE_JSON, CONTENT_PACKAGELOCK_JSON, CONTENT_NPMRC, CONTENT_CDSRC_JSON, CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, SEMVER_REGEX } = require('../../constants')
7
- const { WARNING, ERROR } = BuildTaskHandlerEdmx
7
+ SKIP_PACKAGE_JSON_GENERATION, CONTENT_EDMX, CONTENT_PACKAGE_JSON, CONTENT_PACKAGELOCK_JSON, CONTENT_NPMRC, CONTENT_CDSRC_JSON,
8
+ CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON } = require('../../constants')
9
+ const { WARNING } = BuildTaskHandlerEdmx
8
10
 
9
11
  class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
10
12
  init() {
@@ -25,20 +27,22 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
25
27
  throw new BuildError("Option not supported - compileDest")
26
28
  }
27
29
  // fallback if src has been defined as '.'
28
- this.destSrv = this.isStagingBuild() ? path.resolve(this.task.dest, this.env.folders.srv) : path.join(this.task.dest, FOLDER_GEN)
30
+ this.destSrv = this.isStagingBuild() ? path.resolve(this.task.dest, cds.env.folders.srv) : path.join(this.task.dest, FOLDER_GEN)
29
31
  }
30
32
 
31
33
  options() {
32
- const options = super.options(), { cds } = this
33
- if (cds.requires.extensibility || cds.requires.toggles) options.flavor = 'xtended'
34
+ const options = super.options()
35
+ if (cds.env.requires.extensibility || cds.env.requires.toggles) {
36
+ options.flavor = 'xtended'
37
+ }
34
38
  return options
35
39
  }
36
40
 
37
41
  async build() {
38
- const destSrv = this.isStagingBuild() ? this.destSrv : path.resolve(this.destSrv, this.env.folders.srv)
42
+ const destSrv = this.isStagingBuild() ? this.destSrv : path.resolve(this.destSrv, cds.env.folders.srv)
39
43
  const destRoot = this.isStagingBuild() ? this.task.dest : this.destSrv
40
44
 
41
- if (this.env.odata?.version === ODATA_VERSION_V2) {
45
+ if (cds.env.odata?.version === ODATA_VERSION_V2) {
42
46
  // log warning as nodejs is only supporting odata version V4
43
47
  this.pushMessage("OData v2 is not supported by node runtime. Make sure to define OData v2 in cds configuration.", WARNING)
44
48
  }
@@ -54,12 +58,12 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
54
58
  await this.collectAllLanguageBundles(dictionary, sources, destSrv, destRoot)
55
59
 
56
60
  if (this.hasBuildOption(CONTENT_EDMX, true)) {
57
- const m = await this.cds.load(sources.base, super.options()) // REVISIT: Quick hack to get inferred model as expected by cds.compile.to.edmx()
61
+ const m = await cds.load(sources.base, super.options()) // REVISIT: Quick hack to get inferred model as expected by cds.compile.to.edmx()
58
62
  await this.compileToEdmx(m, this.destSrv)
59
63
  }
60
64
 
61
65
  if (this.isStagingBuild() && !this.hasBuildOption(BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY)) {
62
- const srcSrv = this.task.src === this.buildOptions.root ? path.resolve(this.task.src, this.env.folders.srv) : this.task.src
66
+ const srcSrv = this.task.src === this.buildOptions.root ? path.resolve(this.task.src, cds.env.folders.srv) : this.task.src
63
67
  await this._copyNativeContent(this.buildOptions.root, srcSrv, destRoot, destSrv)
64
68
  }
65
69
  return this._result
@@ -139,7 +143,7 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
139
143
  * @param {*} filter - copy file if filter function returns true
140
144
  */
141
145
  async copyProjectRootContent(src, dest, filter) {
142
- let { folders = ['i18n'] } = this.env.i18n
146
+ let { folders = ['i18n'] } = cds.env.i18n
143
147
  folders.push('handlers')
144
148
  folders = folders.map(folder => path.join(src, folder))
145
149
  let srvAllowList = "package\\.json$" // always copy package.json, modify only if CONTENT_PACKAGE_JSON is true
@@ -159,52 +163,6 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
159
163
  }
160
164
  return srvAllowList.test(entry) && (!filter || filter.call(this, entry))
161
165
  })
162
-
163
- if (this.hasBuildOption(CONTENT_PACKAGE_JSON, true) && fs.existsSync(path.join(dest, 'package.json'))) {
164
- await this._modifyPackageJson(path.join(dest, 'package.json'))
165
- }
166
- }
167
-
168
- /**
169
- * Modify package.json file defined by the given file path.
170
- * @param {string} file
171
- */
172
- async _modifyPackageJson(file) {
173
- function _addEnginesField(content) {
174
- if (!content.engines || !content.engines.node) {
175
- // use minimum node version of @sap/cds
176
- const { engines } = require('../../../../package.json')
177
- if (engines && engines.node) {
178
- this.logger.log(`${this.task.for}: adding node engines version to package.json ${engines.node}`)
179
- content.engines = content.engines || {}
180
- const nodeVersion = NodejsModuleBuilder._convertToClosedVersionRange(engines.node)
181
- if (nodeVersion) {
182
- content.engines.node = nodeVersion
183
- return true
184
- } else {
185
- this.pushMessage(`Invalid node version defined in engines definition for @sap/cds ${engines.node} - skip node engines definition`, ERROR)
186
- }
187
- }
188
- }
189
- return false
190
- }
191
-
192
- let content = await fs.promises.readFile(file)
193
- let changed = false
194
- if (content) {
195
- content = JSON.parse(content)
196
- changed |= _addEnginesField.call(this, content)
197
- }
198
- if (changed) {
199
- await this.write(content).to(file)
200
- }
201
- }
202
-
203
- static _convertToClosedVersionRange(rtVersion) {
204
- rtVersion = rtVersion.trim()
205
- if (rtVersion.match(SEMVER_REGEX)) {
206
- return rtVersion.replace(SEMVER_REGEX, '^$2$3$4')
207
- }
208
166
  }
209
167
  }
210
168
 
package/bin/build/util.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
- const { SEVERITY_ERROR, CDS_MODEL_EXCLUDE_LIST } = require('./constants')
3
+ const cds = require('./cds')
4
+ const { SEVERITY_ERROR, CDS_MODEL_EXCLUDE_LIST, FILE_EXT_CDS } = require('./constants')
4
5
 
5
6
  function getProperty(src, segments) {
6
7
  segments = Array.isArray(segments) ? segments : segments.split('.')
@@ -26,10 +27,12 @@ function setProperty(src, segments, value) {
26
27
  * @param {Array} dirs - the absolute path names to check.
27
28
  */
28
29
  function readPomFilePaths(dirs) {
29
- return dirs.reduce((acc, dir) => {
30
- const file = path.join(dir, 'pom.xml')
31
- if (fs.existsSync(file)) {
32
- acc.push(file)
30
+ return dirs.flat().reduce((acc, dir) => {
31
+ if (dir) {
32
+ const file = path.join(dir, 'pom.xml')
33
+ if (fs.existsSync(file)) {
34
+ acc.push(file)
35
+ }
33
36
  }
34
37
  return acc
35
38
  }, [])
@@ -44,7 +47,7 @@ function hasJavaNature(dirs) {
44
47
  }
45
48
 
46
49
  /**
47
- * Determines wheter the both values are identical.
50
+ * Determines whether the both values are identical.
48
51
  * @param {*} actual
49
52
  * @param {*} expected
50
53
  */
@@ -116,15 +119,10 @@ function _redacted(cred) {
116
119
  }
117
120
 
118
121
  /**
119
- * Distinguishes whether the project uses the new ModelProviderService based approach as
120
- * defined by the new Streamlined MTX (cds >=6) or whether it represents an old MTX project
121
- * using the cds-mtx library.
122
- *
123
- * @param {object} env
124
- * @returns
122
+ * Distinguishes whether the Nodejs project is a Streamlined MTX (cds >=6) or an old MTX project.
125
123
  */
126
- function isStreamlinedMtx(cds) {
127
- if (hasOptionValue(process.env.OLD_MTX, true)) {
124
+ function isStreamlinedMtx() {
125
+ if (cds.utils._oldMtx()) {
128
126
  return false
129
127
  }
130
128
  return (cds.env.requires.toggles
@@ -136,11 +134,10 @@ function isStreamlinedMtx(cds) {
136
134
  /**
137
135
  * Returns a list of fully qualified model names belonging to the '@sap' namespace that cannot be resolved.
138
136
  * E.g. the npm module might NOT be installed.
139
- * @param {*} cds
140
137
  * @param {Array} modelPaths
141
138
  * @returns {Array}
142
139
  */
143
- function resolveRequiredSapModels(cds, modelPaths) {
140
+ function resolveRequiredSapModels(modelPaths) {
144
141
  return modelPaths.filter(p => {
145
142
  if (p.startsWith('@sap/') && !CDS_MODEL_EXCLUDE_LIST.includes(p)) {
146
143
  const files = cds.resolve(p)
@@ -149,6 +146,47 @@ function resolveRequiredSapModels(cds, modelPaths) {
149
146
  })
150
147
  }
151
148
 
149
+ function getDefaultModelOptions(projectPath) {
150
+ const fts = cds.env.features.folders
151
+ const modelPaths = cds.resolve(!fts ? '*' : ['*', fts], false)
152
+ return _pushModelPaths(projectPath, modelPaths)
153
+ }
154
+
155
+ function _pushModelPaths(projectPath, ...modelPaths) {
156
+ const model = new Set()
157
+ // may contain nested arrays
158
+ modelPaths = flatten(modelPaths)
159
+ const { roots } = cds.env
160
+ modelPaths.forEach(m => {
161
+ if (m && !model.has(m) && !model.has(m + "/")) {
162
+ // filter root model paths that do not exist
163
+ // other entries are added as is, e.g. reuse model entries
164
+ if (roots.includes(m)) {
165
+ const dir = path.resolve(projectPath, m)
166
+ if (fs.existsSync(dir)) {
167
+ model.add(m.replace(/\/$/, ''))
168
+ } else if (fs.existsSync(dir + FILE_EXT_CDS)) { //might be cds file name, compatibility to old build configs
169
+ model.add(m)
170
+ }
171
+ } else {
172
+ model.add(m)
173
+ }
174
+ }
175
+ })
176
+ return model
177
+ }
178
+
179
+ function flatten(modelPaths) {
180
+ return modelPaths.reduce((acc, m) => {
181
+ if (Array.isArray(m)) {
182
+ acc = acc.concat(flatten(m))
183
+ } else if (m) {
184
+ acc.push(m)
185
+ }
186
+ return acc
187
+ }, [])
188
+ }
189
+
152
190
  class BuildMessage extends Error {
153
191
  constructor(message, severity = SEVERITY_ERROR) {
154
192
  super(message)
@@ -188,6 +226,8 @@ module.exports = {
188
226
  relativePaths,
189
227
  isStreamlinedMtx,
190
228
  resolveRequiredSapModels,
229
+ getDefaultModelOptions,
230
+ flatten,
191
231
  BuildMessage,
192
232
  BuildError
193
233
  }
@@ -1,3 +1,5 @@
1
+ // eslint-disable-next-line no-console
2
+
1
3
  const cp = require('child_process');
2
4
  const fsp = require('fs').promises;
3
5
  const os = require('os');
@@ -88,9 +90,10 @@ class CfUtil {
88
90
  }
89
91
 
90
92
  if (response.errors) {
91
- const errorMessage = result.errors.map((entry) => `${entry.title}: ${entry.detail} (${entry.code})`).join('\n');
93
+ const errorMessage = response.errors.map((entry) => `${entry.title || ''}: ${entry.detail || ''} (${entry.code || ''})`).join('\n');
92
94
  throw new Error(errorMessage);
93
95
  }
96
+
94
97
  return response;
95
98
  }
96
99
 
@@ -34,7 +34,7 @@ class GitUtil {
34
34
  }
35
35
  }
36
36
 
37
- LOG.log(`adding entry '${file}' to ${GIT_IGNORE_FILE}.`);
37
+ console.log(`adding entry '${file}' to ${GIT_IGNORE_FILE}.`);
38
38
  let gitignore = await this.readFileSafely(gitIgnorePath);
39
39
  gitignore = gitignore + `
40
40
  # added by cds deploy