@sap/cds 6.0.4 → 6.1.2

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 (139) hide show
  1. package/CHANGELOG.md +180 -18
  2. package/apis/cds.d.ts +11 -7
  3. package/apis/log.d.ts +124 -0
  4. package/apis/ql.d.ts +72 -15
  5. package/apis/services.d.ts +13 -2
  6. package/bin/build/buildTaskHandler.js +5 -2
  7. package/bin/build/constants.js +4 -1
  8. package/bin/build/provider/buildTaskHandlerEdmx.js +11 -39
  9. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +14 -34
  10. package/bin/build/provider/buildTaskHandlerInternal.js +56 -4
  11. package/bin/build/provider/buildTaskProviderInternal.js +22 -14
  12. package/bin/build/provider/hana/index.js +12 -9
  13. package/bin/build/provider/java/index.js +18 -8
  14. package/bin/build/provider/mtx/index.js +7 -4
  15. package/bin/build/provider/mtx/resourcesTarBuilder.js +60 -35
  16. package/bin/build/provider/mtx-extension/index.js +57 -0
  17. package/bin/build/provider/mtx-sidecar/index.js +46 -18
  18. package/bin/build/provider/nodejs/index.js +34 -13
  19. package/bin/deploy/to-hana/cfUtil.js +7 -2
  20. package/bin/deploy/to-hana/hana.js +20 -25
  21. package/bin/deploy/to-hana/hdiDeployUtil.js +13 -2
  22. package/bin/serve.js +7 -4
  23. package/lib/compile/{index.js → cds-compile.js} +0 -0
  24. package/lib/compile/extend.js +15 -5
  25. package/lib/compile/minify.js +1 -15
  26. package/lib/compile/parse.js +1 -1
  27. package/lib/compile/resolve.js +2 -2
  28. package/lib/compile/to/srvinfo.js +6 -4
  29. package/lib/{deploy.js → dbs/cds-deploy.js} +9 -8
  30. package/lib/env/{index.js → cds-env.js} +1 -17
  31. package/lib/env/{requires.js → cds-requires.js} +24 -3
  32. package/lib/env/defaults.js +7 -1
  33. package/lib/env/schemas/cds-package.json +11 -0
  34. package/lib/env/schemas/cds-rc.json +614 -0
  35. package/lib/index.js +19 -16
  36. package/lib/log/{errors.js → cds-error.js} +1 -1
  37. package/lib/log/{index.js → cds-log.js} +0 -0
  38. package/lib/log/format/kibana.js +19 -1
  39. package/lib/ql/Query.js +9 -3
  40. package/lib/ql/SELECT.js +2 -2
  41. package/lib/ql/UPDATE.js +2 -2
  42. package/lib/ql/{index.js → cds-ql.js} +4 -10
  43. package/lib/req/context.js +49 -17
  44. package/lib/req/locale.js +5 -1
  45. package/lib/{serve → srv}/adapters.js +23 -19
  46. package/lib/{connect → srv}/bindings.js +0 -0
  47. package/lib/{connect/index.js → srv/cds-connect.js} +1 -1
  48. package/lib/{serve/index.js → srv/cds-serve.js} +0 -0
  49. package/lib/{serve → srv}/factory.js +1 -1
  50. package/lib/{serve/Service-api.js → srv/srv-api.js} +22 -6
  51. package/lib/{serve/Service-dispatch.js → srv/srv-dispatch.js} +13 -8
  52. package/lib/{serve/Service-handlers.js → srv/srv-handlers.js} +10 -0
  53. package/lib/{serve/Service-methods.js → srv/srv-methods.js} +10 -8
  54. package/lib/srv/srv-models.js +207 -0
  55. package/lib/{serve/Transaction.js → srv/srv-tx.js} +57 -40
  56. package/lib/utils/{tests.js → cds-test.js} +2 -2
  57. package/lib/utils/cds-utils.js +146 -0
  58. package/lib/utils/index.js +2 -145
  59. package/lib/utils/jest.js +43 -0
  60. package/lib/utils/resources/index.js +15 -25
  61. package/lib/utils/resources/tar.js +18 -41
  62. package/libx/_runtime/auth/index.js +14 -11
  63. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +7 -19
  64. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -4
  65. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -4
  66. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -4
  67. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -4
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -2
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +6 -19
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +3 -5
  71. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
  72. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -4
  73. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +0 -2
  74. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +3 -1
  75. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +38 -4
  76. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -3
  77. package/libx/_runtime/cds-services/services/utils/differ.js +4 -0
  78. package/libx/_runtime/cds-services/util/errors.js +1 -29
  79. package/libx/_runtime/common/i18n/messages.properties +2 -1
  80. package/libx/_runtime/common/perf/index.js +10 -15
  81. package/libx/_runtime/common/utils/binary.js +3 -4
  82. package/libx/_runtime/common/utils/cqn2cqn4sql.js +0 -1
  83. package/libx/_runtime/common/utils/entityFromCqn.js +8 -5
  84. package/libx/_runtime/common/utils/keys.js +14 -6
  85. package/libx/_runtime/common/utils/resolveView.js +1 -1
  86. package/libx/_runtime/common/utils/template.js +1 -1
  87. package/libx/_runtime/db/Service.js +2 -14
  88. package/libx/_runtime/db/expand/expandCQNToJoin.js +28 -25
  89. package/libx/_runtime/db/expand/rawToExpanded.js +7 -6
  90. package/libx/_runtime/db/generic/input.js +8 -1
  91. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
  92. package/libx/_runtime/db/sql-builder/SelectBuilder.js +37 -18
  93. package/libx/_runtime/extensibility/activate.js +47 -47
  94. package/libx/_runtime/extensibility/add.js +22 -13
  95. package/libx/_runtime/extensibility/addExtension.js +17 -13
  96. package/libx/_runtime/extensibility/defaults.js +25 -30
  97. package/libx/_runtime/extensibility/handler/transformREAD.js +20 -18
  98. package/libx/_runtime/extensibility/linter/allowlist_checker.js +373 -0
  99. package/libx/_runtime/extensibility/linter/annotations_checker.js +113 -0
  100. package/libx/_runtime/extensibility/linter/checker_base.js +20 -0
  101. package/libx/_runtime/extensibility/linter/namespace_checker.js +180 -0
  102. package/libx/_runtime/extensibility/linter.js +32 -0
  103. package/libx/_runtime/extensibility/push.js +77 -20
  104. package/libx/_runtime/extensibility/service.js +29 -12
  105. package/libx/_runtime/extensibility/token.js +57 -0
  106. package/libx/_runtime/extensibility/utils.js +8 -6
  107. package/libx/_runtime/extensibility/validation.js +6 -9
  108. package/libx/_runtime/fiori/generic/new.js +0 -11
  109. package/libx/_runtime/fiori/utils/where.js +1 -1
  110. package/libx/_runtime/hana/Service.js +0 -1
  111. package/libx/_runtime/hana/conversion.js +12 -1
  112. package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +4 -3
  113. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -0
  114. package/libx/_runtime/hana/pool.js +6 -10
  115. package/libx/_runtime/hana/search2Contains.js +0 -5
  116. package/libx/_runtime/hana/search2cqn4sql.js +1 -0
  117. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
  118. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +1 -2
  119. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  120. package/libx/_runtime/messaging/service.js +11 -6
  121. package/libx/_runtime/remote/utils/data.js +5 -0
  122. package/libx/_runtime/sqlite/Service.js +7 -6
  123. package/libx/_runtime/sqlite/execute.js +41 -28
  124. package/libx/odata/afterburner.js +79 -2
  125. package/libx/odata/cqn2odata.js +15 -9
  126. package/libx/odata/grammar.pegjs +157 -76
  127. package/libx/odata/index.js +9 -3
  128. package/libx/odata/parser.js +1 -1
  129. package/libx/odata/utils.js +39 -5
  130. package/libx/rest/RestAdapter.js +3 -7
  131. package/libx/rest/middleware/delete.js +4 -5
  132. package/libx/rest/middleware/parse.js +3 -2
  133. package/package.json +3 -3
  134. package/server.js +1 -1
  135. package/srv/extensibility-service.cds +6 -3
  136. package/srv/model-provider.cds +3 -1
  137. package/srv/model-provider.js +86 -106
  138. package/srv/mtx.js +7 -1
  139. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +0 -240
@@ -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
@@ -3,10 +3,10 @@ const path = require('path')
3
3
  const BuildTaskHandlerEdmx = require('../buildTaskHandlerEdmx')
4
4
  const { BuildError } = require('../../util')
5
5
  const { BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY, ODATA_VERSION, 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 } = require('../../constants')
7
- const { WARNING } = require('../../buildTaskHandler')
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
8
8
 
9
- class NodeCfModuleBuilder extends BuildTaskHandlerEdmx {
9
+ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
10
10
  init() {
11
11
  super.init()
12
12
  // set unified option values in order to ease access later on
@@ -28,6 +28,12 @@ class NodeCfModuleBuilder extends BuildTaskHandlerEdmx {
28
28
  this.destSrv = this.isStagingBuild() ? path.resolve(this.task.dest, this.env.folders.srv) : path.join(this.task.dest, FOLDER_GEN)
29
29
  }
30
30
 
31
+ options() {
32
+ const options = super.options(), {cds} = this
33
+ if (cds.requires.extensibility || cds.requires.toggles) options.flavor = 'xtended'
34
+ return options
35
+ }
36
+
31
37
  async build() {
32
38
  const destSrv = this.isStagingBuild() ? this.destSrv : path.resolve(this.destSrv, this.env.folders.srv)
33
39
  const destRoot = this.isStagingBuild() ? this.task.dest : this.destSrv
@@ -48,7 +54,8 @@ class NodeCfModuleBuilder extends BuildTaskHandlerEdmx {
48
54
  await this.collectAllLanguageBundles(dictionary, sources, destSrv, destRoot)
49
55
 
50
56
  if (this.hasBuildOption(CONTENT_EDMX, true)) {
51
- await this.compileToEdmx(dictionary.base, this.destSrv)
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()
58
+ await this.compileToEdmx(m, this.destSrv)
52
59
  }
53
60
 
54
61
  if (!this.hasBuildOption(BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY)) {
@@ -77,11 +84,11 @@ class NodeCfModuleBuilder extends BuildTaskHandlerEdmx {
77
84
  }
78
85
 
79
86
  /**
80
- * Copy files from the given <em>src</em>' folder (e.g. 'project/srv') to either <em>destRoot</em> (e.g. 'project/gen/srv')
87
+ * Copy files from the given <em>src</em>' folder (e.g. 'project/srv') to either <em>destRoot</em> (e.g. 'project/gen/srv')
81
88
  * or <em>destSrv</em> (e.g. 'project/gen/srv/srv') folders according to the file semantics.
82
- * Files with project semantics like 'package.json' or '.npmrc' file are copied to <em>destRoot</em> while others like '.js' service handlers
89
+ * Files with project semantics like 'package.json' or '.npmrc' file are copied to <em>destRoot</em> while others like '.js' service handlers
83
90
  * are copied to <em>destSrv</em>.
84
- * @param {*} src
91
+ * @param {*} src
85
92
  * @param {*} destRoot - folder name representing the app root folder (e.g. gen/srv)
86
93
  * @param {*} destSrv - folder name representing the app sub-folder (e.g. gen/srv/srv)
87
94
  * @returns the list of files that have been copied
@@ -98,7 +105,8 @@ class NodeCfModuleBuilder extends BuildTaskHandlerEdmx {
98
105
  // TODO shall not copy language bundles - return !/(\/|\\)(node_modules|_i18n)(\/|\\)?$/.test(entry)
99
106
  return !/(\/|\\)node_modules(\/|\\)?$/.test(entry)
100
107
  }
101
- if (srvRootBlockList.test(entry)) {
108
+ // make sure the file exists on srv root level - see https://github.tools.sap/cap/issues/issues/12077
109
+ if (srvRootBlockList.test(entry) && path.dirname(entry) === src) {
102
110
  srvRootFileNames.push(path.basename(entry))
103
111
  return false
104
112
  }
@@ -125,8 +133,8 @@ class NodeCfModuleBuilder extends BuildTaskHandlerEdmx {
125
133
  /**
126
134
  * Copy dedicated files (files with project semantics like package.json, .npmrc, .cdsrc, etc.)
127
135
  * from the given <em>src</em> folder (e.g. 'project') into the given <em>dest</em> folder (e.g. 'project/gen/srv')
128
- * @param {*} src
129
- * @param {*} dest
136
+ * @param {*} src
137
+ * @param {*} dest
130
138
  * @param {*} filter - copy file if filter function returns true
131
139
  */
132
140
  async _copyProjectRootContent(src, dest, filter) {
@@ -163,12 +171,18 @@ class NodeCfModuleBuilder extends BuildTaskHandlerEdmx {
163
171
  async _modifyPackageJson(file) {
164
172
  function _addEnginesField(content) {
165
173
  if (!content.engines || !content.engines.node) {
174
+ // use minimum node version of @sap/cds
166
175
  const { engines } = require('../../../../package.json')
167
176
  if (engines && engines.node) {
168
177
  this.logger.log(`${this.task.for}: adding node engines version to package.json ${engines.node}`)
169
178
  content.engines = content.engines || {}
170
- content.engines.node = engines.node
171
- return true
179
+ const nodeVersion = NodejsModuleBuilder._convertToClosedVersionRange(engines.node)
180
+ if (nodeVersion) {
181
+ content.engines.node = nodeVersion
182
+ return true
183
+ } else {
184
+ this.pushMessage(`Invalid node version defined in engines definition for @sap/cds ${engines.node} - skip node engines definition`, ERROR)
185
+ }
172
186
  }
173
187
  }
174
188
  return false
@@ -184,6 +198,13 @@ class NodeCfModuleBuilder extends BuildTaskHandlerEdmx {
184
198
  await this.write(content).to(file)
185
199
  }
186
200
  }
201
+
202
+ static _convertToClosedVersionRange(rtVersion) {
203
+ rtVersion = rtVersion.trim()
204
+ if (rtVersion.match(SEMVER_REGEX)) {
205
+ return rtVersion.replace(SEMVER_REGEX, '^$2$3$4')
206
+ }
207
+ }
187
208
  }
188
209
 
189
- module.exports = NodeCfModuleBuilder
210
+ module.exports = NodejsModuleBuilder
@@ -77,7 +77,12 @@ class CfUtil {
77
77
  async _cfRequest(urlPath, queryObj, bodyObj) {
78
78
  if (queryObj) {
79
79
  const entries = Object.entries(queryObj);
80
- const queryStr = entries.map((entry) => `${entry[0]}=${encodeURIComponent(entry[1])}`).join('&');
80
+ const queryStr = entries.map(([key, value]) => {
81
+ // commas cause problems in cf curl when not double encoded
82
+ value = value.replace(/,/g, encodeURIComponent(','));
83
+ return `${key}=${encodeURIComponent(value)}`;
84
+ }).join('&');
85
+
81
86
  urlPath = urlPath + `?${queryStr}`;
82
87
  }
83
88
 
@@ -125,7 +130,7 @@ class CfUtil {
125
130
  async getCfTargetFromCli() {
126
131
  const result = await this._cfRun('target');
127
132
  return {
128
- apiEndpoint: this._extract(result, /api endpoint\s*:\s*(.+)/i, `CF API endpoint is missing. Use 'cf login' to login.`),
133
+ apiEndpoint: this._extract(result, /api endpoint\s*:\s*([^\s]+)/i, `CF API endpoint is missing. Use 'cf login' to login.`),
129
134
  user: this._extract(result, /user\s*:\s*(.+)/i, `CF user is missing. Use 'cf login' to login.`),
130
135
  org: this._extract(result, /org\s*:\s*(.+)/i, `CF org is missing. Use 'cf target -o <ORG> to specify.`),
131
136
  space: this._extract(result, /space\s*:\s*(.+)/i, `CF space is missing. Use 'cf target -s <SPACE>' to specify.`),
@@ -35,7 +35,7 @@ class HanaDeployer {
35
35
  this.logger = logger;
36
36
 
37
37
  this.logger.log(`${bold('Starting deploy to SAP HANA ...')}`);
38
- if (vcapFile) {
38
+ if (vcapFile) {
39
39
  this.logger.log();
40
40
  this.logger.log(`Using VCAP_SERVICES from file ${vcapFile} (beta feature).`);
41
41
  bindCallback = null // credentials are given - no cds bind then
@@ -49,9 +49,9 @@ class HanaDeployer {
49
49
 
50
50
  const { buildResults } = await this._build(buildTaskOptions, model);
51
51
 
52
- let vcapServices;
52
+ let vcapFileEnv;
53
53
  if (vcapFile) {
54
- vcapServices = await this._loadVCAPServices(vcapFile);
54
+ vcapFileEnv = await this._loadDefaultEnv(vcapFile);
55
55
  }
56
56
 
57
57
  for (const buildResult of buildResults) {
@@ -71,7 +71,7 @@ class HanaDeployer {
71
71
  serviceKeyName = cfServiceInstanceKeyName;
72
72
  serviceName = serviceName || cfServiceInstanceName;
73
73
 
74
- vcapServices = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
74
+ vcapFileEnv = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
75
75
 
76
76
  if (!noSave && !bindCallback) {
77
77
  await this._addInstanceToDefaultEnvJson([currentModelFolder, projectPath], cfServiceInstanceName, serviceKey);
@@ -95,7 +95,7 @@ class HanaDeployer {
95
95
  });
96
96
  }
97
97
 
98
- await hdiDeployUtil.deploy(currentModelFolder, vcapServices, hdiOptions);
98
+ await hdiDeployUtil.deploy(currentModelFolder, vcapFileEnv, hdiOptions);
99
99
 
100
100
  if (bindCallback) {
101
101
  const args = [path.relative(projectPath, buildResult.task.src)];
@@ -204,16 +204,12 @@ class HanaDeployer {
204
204
  }
205
205
 
206
206
 
207
- async _loadVCAPServices(vcapFile) {
207
+ async _loadDefaultEnv(defaultEnvFile) {
208
208
  try {
209
- const content = JSON.parse(await fs.readFile(vcapFile));
210
- if (!content.VCAP_SERVICES) {
211
- throw new Error(`The vcap file ${vcapFile} does not contain a VCAP_SERVICES entry.`);
212
- }
213
-
214
- return content.VCAP_SERVICES;
209
+ const content = await fs.readFile(defaultEnvFile);
210
+ return JSON.parse(content);
215
211
  } catch (err) {
216
- throw new Error(`Error reading vcap file: ${err.message}`);
212
+ throw new Error(`Error reading default env file: ${err.message}`);
217
213
  }
218
214
  }
219
215
 
@@ -237,13 +233,10 @@ class HanaDeployer {
237
233
  }
238
234
 
239
235
  const hanaEntry = this._getVCAPServicesEntry(serviceInstanceName, serviceKey)
240
- defaultEnvJson.VCAP_SERVICES = {
241
- ...defaultEnvJson.VCAP_SERVICES,
242
- ...hanaEntry
243
- }
236
+ Object.assign(defaultEnvJson, hanaEntry);
244
237
 
245
238
  this.logger.log(`Writing ${defaultEnvJsonPath}`);
246
- await fs.mkdir(path.dirname(defaultEnvJsonPath), {recursive: true})
239
+ await fs.mkdir(path.dirname(defaultEnvJsonPath), { recursive: true })
247
240
  await fs.writeFile(defaultEnvJsonPath, JSON.stringify(defaultEnvJson, null, 2));
248
241
  }
249
242
  }
@@ -251,13 +244,15 @@ class HanaDeployer {
251
244
 
252
245
  _getVCAPServicesEntry(serviceInstanceName, serviceKey) {
253
246
  return {
254
- hana: [
255
- {
256
- name: serviceInstanceName,
257
- tags: ['hana'],
258
- credentials: serviceKey
259
- }
260
- ]
247
+ VCAP_SERVICES: {
248
+ hana: [
249
+ {
250
+ name: serviceInstanceName,
251
+ tags: ['hana'],
252
+ credentials: serviceKey
253
+ }
254
+ ]
255
+ }
261
256
  };
262
257
  }
263
258
 
@@ -20,7 +20,10 @@ class HdiDeployUtil {
20
20
  await this._executeDeploy(dbDir, env, logger);
21
21
  }
22
22
 
23
- async deploy(dbDir, vcapServices, options = {}) {
23
+ async deploy(dbDir, vcapEnv, options) {
24
+ vcapEnv = vcapEnv || {}; // handles null and undefined
25
+ options = options || {};
26
+
24
27
  LOG.log();
25
28
  LOG.log(`Deploying to HANA from ${dbDir}`);
26
29
 
@@ -34,7 +37,15 @@ class HdiDeployUtil {
34
37
  deployerEnv = hdiDeployLib.clean_env(deployerEnv);
35
38
  }
36
39
 
37
- deployerEnv.VCAP_SERVICES = JSON.stringify(vcapServices);
40
+ if (vcapEnv.VCAP_SERVICES) {
41
+ deployerEnv.VCAP_SERVICES = JSON.stringify(vcapEnv.VCAP_SERVICES);
42
+ }
43
+ if (vcapEnv.SERVICE_REPLACEMENTS) {
44
+ deployerEnv.SERVICE_REPLACEMENTS = JSON.stringify(vcapEnv.SERVICE_REPLACEMENTS);
45
+ }
46
+ if (vcapEnv.TARGET_CONTAINER) {
47
+ deployerEnv.TARGET_CONTAINER = vcapEnv.TARGET_CONTAINER;
48
+ }
38
49
 
39
50
  if (options.autoUndeploy) {
40
51
  LOG.log(`Hdi deployer automatically undeploys deleted resources using --auto-undeploy.`);
package/bin/serve.js CHANGED
@@ -181,7 +181,7 @@ async function serve (all=[], o={}) { // NOSONAR
181
181
  log ('loading plugin:', {impl})
182
182
  // TODO support ESM plugins. But see cap/cds/pull/1838#issuecomment-1177200 !
183
183
  const plugin = require(impl)
184
- if (plugin.activate) plugin.activate(each)
184
+ if (plugin.activate) await plugin.activate(each)
185
185
  }
186
186
 
187
187
  // bootstrap server from project-local server.js or from @sap/cds/server.js
@@ -258,16 +258,19 @@ function _prepare_logging () { // NOSONAR
258
258
  cds.on ('serving', (srv) => {
259
259
  const details = {}
260
260
  if (srv.path) details.path = srv.path
261
- if (srv._source) details.impl = local( srv._source.startsWith('@sap') ? require.resolve(srv._source) : srv._source )
261
+ if (srv._source && !srv._source.startsWith('@sap'))
262
+ details.impl = local(srv._source)
262
263
  log (`${srv.mocked ? 'mocking' : 'serving'} ${srv.name}`, details)
263
264
  })
264
265
 
266
+ cds.on ('served', ()=> console.log())
267
+
265
268
  // print info when we are finally on air
266
269
  cds.once ('listening', ({url})=>{
267
- console.log ()
270
+ console.log()
268
271
  log ('server listening on',{url})
269
272
  _timer && console.timeEnd (_timer)
270
- if (process.stdin.isTTY) log (`[ terminate with ^C ]`)
273
+ if (process.stdin.isTTY) log (`[ terminate with ^C ]\n`)
271
274
  })
272
275
  }
273
276
 
File without changes
@@ -1,8 +1,18 @@
1
+ const compile = require ('./cds-compile')
1
2
  const { extend } = require ('../lazy')
2
- const compile = require ('./index')
3
3
 
4
- module.exports = o => o.definitions ? { with(...exts) {
5
- const all = { 'base.csn': JSON.stringify(o) }
6
- exts.forEach ((x,i)=> all[i+'.csn'] = JSON.stringify(x))
7
- return compile (all)
4
+ module.exports = o => o.definitions ? { with(...csns) {
5
+
6
+ const csn=o, merged = { definitions: {}, extensions: [] }
7
+ for (const { definitions, extensions } of csns) {
8
+ if (definitions) Object.assign(merged.definitions, definitions)
9
+ if (extensions) merged.extensions.push(...extensions)
10
+ }
11
+ const extended = compile({
12
+ 'base.csn': compile.to.json(csn),
13
+ 'ext.csn': compile.to.json(merged)
14
+ })
15
+ extended.$sources = csn.$sources // required to load resources like i18n later on
16
+ return extended
17
+
8
18
  }} : extend(o)
@@ -35,7 +35,7 @@ module.exports = function cds_minify (csn, _roots) { // IMPORTANT: don't add cds
35
35
  }
36
36
  function _visit (d) {
37
37
  if (typeof d === 'string') {
38
- if (isInReservedNamespace(d)) return
38
+ if (cds.compiler.model.isInReservedNamespace(d)) return
39
39
  else d = all[d]
40
40
  } else if (d.ref) return d.ref.reduce((p,n) => {
41
41
  let d = (p.elements || csn.definitions[p.target].elements)[n.id || n] // > n.id -> view with parameters
@@ -61,18 +61,4 @@ module.exports = function cds_minify (csn, _roots) { // IMPORTANT: don't add cds
61
61
  return minified
62
62
  }
63
63
 
64
- /**
65
- * TODO - keep in sync with @sap/cds-compiler. Use their public API once it is available!
66
- * Checks whether the given absolute path is inside a reserved namespace.
67
- *
68
- * @param {string} absolute
69
- * @returns {boolean}
70
- */
71
- function isInReservedNamespace(absolute) {
72
- return absolute.startsWith( 'cds.') &&
73
- !absolute.match(/^cds\.foundation(\.|$)/) &&
74
- !absolute.match(/^cds\.outbox(\.|$)/) && // Requested by Node runtime
75
- !absolute.match(/^cds\.xt(\.|$)/); // Requested by Mtx
76
- }
77
-
78
64
  const _minified = Symbol('minified')
@@ -56,7 +56,7 @@ const tagged = (parse, strings, ...values) => {
56
56
  all[2*i] = s
57
57
  all[2*i+1] = v instanceof cds.entity ? v.name : ':'+i
58
58
  if (typeof v === 'string' && s.endsWith(' like ') && !v.includes('%')) values[i] = `%${v}%`
59
- if (Array.isArray(v) && s.endsWith(' in ')) values[i] = {list: v.map(cxn4)}
59
+ if (Array.isArray(v) && s.toLowerCase().endsWith(' in ')) values[i] = {list: v.map(cxn4)}
60
60
  }
61
61
  all[2*i] = strings[i]
62
62
  return merge (parse(all.join('')), values)
@@ -20,7 +20,7 @@ module.exports = exports = function cds_resolve (model, o={}) { // NOSONAR
20
20
  if (model === '*') return _resolve_all(o,this)
21
21
  if (Array.isArray(model)) {
22
22
  const resolved = [... new Set(model)] .reduce ((prev,next) => prev.concat (this.resolve(next,o)||[]), [])
23
- return o.dry || o === false ? resolved : _resolved (resolved)
23
+ return o.dry || o === false ? [...new Set(resolved.flat())] : _resolved (resolved)
24
24
  }
25
25
  if (model.endsWith('/*')) return _resolve_subdirs_in(model,o,this)
26
26
 
@@ -69,7 +69,7 @@ const _required = (cds,env=cds.env) => Object.values(env.requires) .map (r => r.
69
69
  const _resolve = require('module')._resolveFilename
70
70
 
71
71
  function _resolve_all (o,cds) {
72
- const {roots} = o.env || cds.env; if (o.dry || o === false) return [ ...roots, ..._required(cds) ]
72
+ const {roots} = o.env || cds.env; if (o.dry || o === false) return [ ...roots, ...new Set(_required(cds).flat()) ]
73
73
  const cache = o.cache || exports.cache
74
74
  const cached = cache['*']; if (cached) return cached
75
75
  cache['*'] = [] // important to avoid endless recursion on '*'
@@ -69,21 +69,23 @@ module.exports = (model, options={}) => {
69
69
 
70
70
  function _javaPrefix() {
71
71
  let is_java
72
+ const javaPrefixDefault = 'odata/v4/'
72
73
  for (let s of model.$sources) {
73
74
  const file = isfile (join (s,'../src/main/resources/application.yaml'))
74
75
  if (file) {
75
76
  const yaml = cds.load.yaml(file)
76
- for (let {cds} of Array.isArray(yaml) ? yaml : [yaml]) {
77
+ for (let yamlDoc of Array.isArray(yaml) ? yaml : [yaml]) {
78
+ const cds = yamlDoc?.cds;
77
79
  if (!cds) continue
78
80
  return cds['odataV4.endpoint.path'] || cds['odataV2.endpoint.path'] // https://cap.cloud.sap/docs/java/application-services#configure-base-path
79
81
  || cds['odata-v4.endpoint.path'] || cds['odata-v2.endpoint.path'] // older/intermediate config, keep for backward compatibility
80
-
82
+ || javaPrefixDefault
81
83
  }
82
- return 'odata/v4/'
84
+ return javaPrefixDefault
83
85
  }
84
86
  else if (isfile (join(s,'../pom.xml'))) is_java = true
85
87
  }
86
- return is_java && 'odata/v4/'
88
+ return is_java && javaPrefixDefault
87
89
  }
88
90
 
89
91
  }
@@ -1,4 +1,4 @@
1
- const cds = require('./index'), { local, inspect } = cds.utils
1
+ const cds = require('../index'), { local, inspect } = cds.utils
2
2
  const DEBUG = cds.debug('deploy')
3
3
  /* eslint-disable no-console */
4
4
 
@@ -111,7 +111,7 @@ exports.create = async function (db, csn=db.model, o) {
111
111
  console.log(); for (let each of drops) console.log(each)
112
112
  console.log(); for (let each of creates) console.log(each,'\n')
113
113
  return
114
- } else return db.tx (async tx => {
114
+ } else return db.run (async tx => {
115
115
  await tx.run(drops)
116
116
  await tx.run(creates)
117
117
  return true
@@ -120,8 +120,9 @@ exports.create = async function (db, csn=db.model, o) {
120
120
  }
121
121
 
122
122
 
123
- exports.init = (db, csn=db.model, log=()=>{}) => db.tx (async tx => {
124
- const resources = await exports.resources(csn), inits=[]
123
+ exports.init = (db, csn=db.model, log=()=>{}) => db.run (async tx => {
124
+ const resources = await exports.resources(csn, {testdata: cds.env.features.test_data})
125
+ const inits=[]
125
126
  for (let [file,e] of Object.entries(resources)) {
126
127
  if (e === '*') { // init.js/ts
127
128
  let x = await cds.utils._import(file); if (!x) continue
@@ -143,9 +144,9 @@ exports.init = (db, csn=db.model, log=()=>{}) => db.tx (async tx => {
143
144
  })
144
145
 
145
146
 
146
- exports.resources = async function (csn) {
147
+ exports.resources = async function (csn, opts) {
147
148
  if (!csn || !csn.definitions) csn = await cds.load (csn||'*') .then (cds.minify)
148
- const folders = await exports.resources.folders(csn)
149
+ const folders = await exports.resources.folders(csn, opts)
149
150
  const found={}, ts = process.env.CDS_TYPESCRIPT
150
151
  for (let folder of folders) {
151
152
  // fetching init.js files
@@ -173,11 +174,11 @@ exports.resources = async function (csn) {
173
174
  }
174
175
 
175
176
 
176
- exports.resources.folders = async function (csn) {
177
+ exports.resources.folders = async function (csn, o={}) {
177
178
  if (!csn || !csn.definitions) csn = await cds.load (csn||'*') .then (cds.minify)
178
179
  const folders = new Set (csn.$sources.map (path.dirname) .filter (f => f !== cds.home))
179
180
  if (cds.env.folders.db) folders.add (path.resolve(cds.root, cds.env.folders.db))
180
- if (cds.env.features.test_data) folders.add (path.resolve(cds.root,'test/'))
181
+ if (o.testdata) folders.add (path.resolve(cds.root,'test/'))
181
182
  return folders
182
183
  }
183
184