@sap/cds 6.1.2 → 6.2.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 (212) hide show
  1. package/CHANGELOG.md +92 -8
  2. package/apis/cds.d.ts +18 -6
  3. package/apis/connect.d.ts +1 -1
  4. package/apis/cqn.d.ts +1 -1
  5. package/apis/log.d.ts +23 -5
  6. package/apis/ql.d.ts +128 -61
  7. package/apis/services.d.ts +11 -0
  8. package/apis/test.d.ts +61 -0
  9. package/apis/utils.d.ts +15 -0
  10. package/app/fiori/preview.js +1 -0
  11. package/bin/build/buildTaskEngine.js +70 -22
  12. package/bin/build/buildTaskFactory.js +18 -11
  13. package/bin/build/buildTaskHandler.js +1 -1
  14. package/bin/build/buildTaskProviderFactory.js +3 -13
  15. package/bin/build/constants.js +0 -1
  16. package/bin/build/index.js +14 -6
  17. package/bin/build/provider/buildTaskHandlerEdmx.js +2 -3
  18. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +2 -2
  19. package/bin/build/provider/buildTaskHandlerInternal.js +3 -6
  20. package/bin/build/provider/buildTaskProviderInternal.js +51 -39
  21. package/bin/build/provider/fiori/index.js +3 -3
  22. package/bin/build/provider/hana/2migration.js +1 -1
  23. package/bin/build/provider/hana/index.js +34 -27
  24. package/bin/build/provider/java/index.js +6 -7
  25. package/bin/build/provider/mtx/index.js +20 -18
  26. package/bin/build/provider/mtx/resourcesTarBuilder.js +8 -11
  27. package/bin/build/provider/mtx-sidecar/index.js +13 -17
  28. package/bin/build/provider/nodejs/index.js +8 -7
  29. package/bin/build/util.js +22 -4
  30. package/bin/cds.js +8 -4
  31. package/bin/deploy/to-hana/cfUtil.js +53 -18
  32. package/bin/mtx/in-cds.js +1 -0
  33. package/bin/serve.js +37 -30
  34. package/lib/auth/basic-auth.js +33 -0
  35. package/lib/auth/dummy-auth.js +7 -0
  36. package/lib/auth/ias-auth.js +2 -0
  37. package/lib/auth/index.js +31 -0
  38. package/lib/auth/jwt-auth.js +3 -0
  39. package/lib/auth/mocked-users.js +72 -0
  40. package/lib/auth/passport-basic.js +12 -0
  41. package/lib/auth/passport-digest.js +14 -0
  42. package/lib/auth/xsuaa-auth.js +3 -0
  43. package/lib/compile/cds-compile.js +3 -3
  44. package/lib/compile/to/cdl.js +5 -1
  45. package/lib/compile/to/edm.js +8 -0
  46. package/lib/compile/to/gql.js +1 -0
  47. package/lib/compile/to/json.js +30 -5
  48. package/lib/compile/to/sql.js +3 -1
  49. package/lib/core/index.js +5 -1
  50. package/lib/dbs/cds-deploy.js +36 -6
  51. package/lib/env/cds-env.js +15 -5
  52. package/lib/env/cds-requires.js +51 -58
  53. package/lib/env/defaults.js +1 -0
  54. package/lib/env/schemas/cds-package.json +4 -0
  55. package/lib/env/schemas/cds-rc.json +63 -77
  56. package/lib/i18n/localize.js +16 -5
  57. package/lib/index.js +9 -4
  58. package/lib/log/cds-error.js +4 -6
  59. package/lib/log/cds-log.js +89 -53
  60. package/lib/log/service/index.js +1 -0
  61. package/lib/ql/CREATE.js +2 -5
  62. package/lib/ql/DELETE.js +1 -1
  63. package/lib/ql/DROP.js +1 -3
  64. package/lib/ql/INSERT.js +3 -3
  65. package/lib/ql/Query.js +10 -23
  66. package/lib/ql/SELECT.js +1 -2
  67. package/lib/ql/UPDATE.js +2 -2
  68. package/lib/ql/Whereable.js +7 -15
  69. package/lib/ql/cds-ql.js +9 -3
  70. package/lib/req/cds-context.js +11 -3
  71. package/lib/req/context.js +29 -23
  72. package/lib/req/locale.js +9 -5
  73. package/lib/req/request.js +1 -0
  74. package/lib/req/user.js +2 -1
  75. package/lib/srv/cds-connect.js +1 -1
  76. package/lib/srv/cds-serve.js +21 -14
  77. package/lib/srv/middlewares/cds-context.js +29 -0
  78. package/lib/srv/middlewares/ctx-model.js +24 -0
  79. package/lib/srv/middlewares/errors.js +9 -0
  80. package/lib/srv/middlewares/index.js +22 -0
  81. package/lib/srv/middlewares/sap-statistics.js +13 -0
  82. package/lib/srv/middlewares/trace.js +102 -0
  83. package/lib/srv/protocols/_legacy.js +42 -0
  84. package/lib/srv/protocols/graphql.js +39 -0
  85. package/lib/srv/protocols/hcql.js +37 -0
  86. package/lib/srv/protocols/index.js +86 -0
  87. package/lib/srv/protocols/odata-v2-proxy.js +3767 -0
  88. package/lib/srv/protocols/odata-v2.js +26 -0
  89. package/lib/srv/protocols/odata-v4.js +16 -0
  90. package/lib/srv/protocols/rest.js +13 -0
  91. package/lib/srv/srv-api.js +5 -0
  92. package/lib/srv/srv-models.js +4 -6
  93. package/lib/utils/axios.js +3 -2
  94. package/lib/utils/cds-test.js +27 -21
  95. package/lib/utils/cds-utils.js +19 -20
  96. package/lib/utils/tar.js +175 -0
  97. package/libx/_runtime/audit/generic/personal/utils.js +18 -7
  98. package/libx/_runtime/audit/utils/v2.js +1 -0
  99. package/libx/_runtime/auth/index.js +4 -0
  100. package/libx/_runtime/auth/strategies/ias-auth.js +76 -0
  101. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +8 -3
  102. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +15 -4
  103. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
  104. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +1 -1
  105. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
  106. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +9 -0
  107. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriInfo.js +5 -1
  108. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +12 -0
  109. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js +6 -2
  110. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/RequestValidator.js +47 -7
  111. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  112. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +0 -2
  113. package/libx/_runtime/cds-services/services/utils/compareJson.js +3 -1
  114. package/libx/_runtime/cds-services/util/assert.js +7 -0
  115. package/libx/_runtime/common/aspects/relation.js +1 -1
  116. package/libx/_runtime/common/composition/data.js +61 -15
  117. package/libx/_runtime/common/composition/delete.js +0 -1
  118. package/libx/_runtime/common/composition/insert.js +0 -1
  119. package/libx/_runtime/common/composition/tree.js +4 -10
  120. package/libx/_runtime/common/composition/update.js +44 -21
  121. package/libx/_runtime/common/generic/auth/capabilities.js +8 -10
  122. package/libx/_runtime/common/generic/crud.js +1 -2
  123. package/libx/_runtime/common/generic/etag.js +4 -4
  124. package/libx/_runtime/common/generic/input.js +21 -6
  125. package/libx/_runtime/common/generic/paging.js +3 -3
  126. package/libx/_runtime/common/generic/put.js +7 -4
  127. package/libx/_runtime/common/generic/sorting.js +4 -4
  128. package/libx/_runtime/common/generic/temporal.js +3 -6
  129. package/libx/_runtime/common/i18n/messages.properties +0 -7
  130. package/libx/_runtime/common/utils/cqn2cqn4sql.js +11 -6
  131. package/libx/_runtime/common/utils/csn.js +0 -28
  132. package/libx/_runtime/common/utils/draft.js +8 -1
  133. package/libx/_runtime/common/utils/path.js +7 -1
  134. package/libx/_runtime/common/utils/propagateForeignKeys.js +122 -0
  135. package/libx/_runtime/common/utils/resolveView.js +2 -3
  136. package/libx/_runtime/common/utils/template.js +2 -3
  137. package/libx/_runtime/db/data-conversion/post-processing.js +3 -44
  138. package/libx/_runtime/db/generic/input.js +6 -6
  139. package/libx/_runtime/db/sql-builder/dataTypes.js +4 -0
  140. package/libx/_runtime/fiori/generic/activate.js +2 -2
  141. package/libx/_runtime/fiori/generic/before.js +40 -72
  142. package/libx/_runtime/fiori/generic/cancel.js +2 -2
  143. package/libx/_runtime/fiori/generic/delete.js +2 -2
  144. package/libx/_runtime/fiori/generic/edit.js +2 -2
  145. package/libx/_runtime/fiori/generic/new.js +3 -5
  146. package/libx/_runtime/fiori/generic/patch.js +49 -43
  147. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  148. package/libx/_runtime/fiori/generic/read.js +27 -37
  149. package/libx/_runtime/fiori/utils/where.js +4 -2
  150. package/libx/_runtime/hana/Service.js +1 -3
  151. package/libx/_runtime/hana/conversion.js +3 -0
  152. package/libx/_runtime/hana/driver.js +33 -3
  153. package/libx/_runtime/hana/dynatrace.js +1 -0
  154. package/libx/_runtime/hana/search2Contains.js +12 -1
  155. package/libx/_runtime/hana/search2cqn4sql.js +10 -27
  156. package/libx/_runtime/hana/streaming.js +1 -0
  157. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
  158. package/libx/_runtime/messaging/common-utils/AMQPClient.js +1 -0
  159. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +5 -2
  160. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -0
  161. package/libx/_runtime/messaging/enterprise-messaging.js +62 -3
  162. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  163. package/libx/_runtime/messaging/redis-messaging.js +1 -0
  164. package/libx/_runtime/remote/Service.js +2 -2
  165. package/libx/_runtime/remote/utils/client.js +35 -11
  166. package/libx/_runtime/remote/utils/data.js +7 -2
  167. package/libx/_runtime/sqlite/Service.js +18 -7
  168. package/libx/_runtime/sqlite/conversion.js +3 -0
  169. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +3 -3
  170. package/libx/_runtime/sqlite/localized.js +8 -8
  171. package/libx/odata/afterburner.js +39 -7
  172. package/libx/odata/cqn2odata.js +6 -3
  173. package/libx/odata/grammar.pegjs +66 -18
  174. package/libx/odata/index.js +3 -2
  175. package/libx/odata/parser.js +1 -1
  176. package/libx/odata/utils.js +2 -0
  177. package/libx/rest/RestAdapter.js +62 -43
  178. package/libx/rest/middleware/input.js +2 -3
  179. package/libx/rest/middleware/parse.js +2 -1
  180. package/libx/rest/middleware/update.js +1 -1
  181. package/package.json +2 -2
  182. package/server.js +5 -4
  183. package/srv/mtx.cds +1 -1
  184. package/srv/mtx.js +4 -24
  185. package/lib/srv/adapters.js +0 -85
  186. package/lib/utils/resources/index.js +0 -48
  187. package/lib/utils/resources/tar.js +0 -49
  188. package/lib/utils/resources/utils.js +0 -11
  189. package/libx/_runtime/db/utils/propagateForeignKeys.js +0 -93
  190. package/libx/_runtime/extensibility/activate.js +0 -69
  191. package/libx/_runtime/extensibility/add.js +0 -50
  192. package/libx/_runtime/extensibility/addExtension.js +0 -72
  193. package/libx/_runtime/extensibility/defaults.js +0 -34
  194. package/libx/_runtime/extensibility/handler/transformREAD.js +0 -121
  195. package/libx/_runtime/extensibility/handler/transformRESULT.js +0 -51
  196. package/libx/_runtime/extensibility/handler/transformWRITE.js +0 -64
  197. package/libx/_runtime/extensibility/linter/allowlist_checker.js +0 -373
  198. package/libx/_runtime/extensibility/linter/annotations_checker.js +0 -113
  199. package/libx/_runtime/extensibility/linter/checker_base.js +0 -20
  200. package/libx/_runtime/extensibility/linter/namespace_checker.js +0 -180
  201. package/libx/_runtime/extensibility/linter.js +0 -32
  202. package/libx/_runtime/extensibility/push.js +0 -118
  203. package/libx/_runtime/extensibility/service.js +0 -38
  204. package/libx/_runtime/extensibility/token.js +0 -57
  205. package/libx/_runtime/extensibility/utils.js +0 -131
  206. package/libx/_runtime/extensibility/validation.js +0 -50
  207. package/libx/_runtime/extensibility/views.js +0 -12
  208. package/srv/extensibility-service.cds +0 -59
  209. package/srv/extensibility-service.js +0 -1
  210. package/srv/extensions.cds +0 -8
  211. package/srv/model-provider.cds +0 -61
  212. package/srv/model-provider.js +0 -143
@@ -79,7 +79,7 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
79
79
  }
80
80
 
81
81
  /**
82
- * Deletes any content that has been created in folder '#this.task.dest/src/gen' by some inplace mode.
82
+ * Deletes any content that has been created in folder '#this.task.dest/src/gen' by inplace mode.
83
83
  * <br>
84
84
  * Note: Content created in staging build will be deleted by the #BuildTaskEngine itself.
85
85
  */
@@ -91,7 +91,7 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
91
91
  }
92
92
 
93
93
  /**
94
- * Copies all files located at <src> (except HANA artefacts not contained in <db>/src/**) to the folder <dest>.
94
+ * Copies all files located at <src> (except HANA artifacts not contained in <db>/src/**) to the folder <dest>.
95
95
  * '*.csv' files are read based on the corresponding CDS model file location and copied as flat list into folder '<dest>/src/gen>'.
96
96
  *
97
97
  * @param {string} src
@@ -116,22 +116,24 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
116
116
  })
117
117
  await Promise.all(promises)
118
118
 
119
- // 2. copy files except *.cds, *.csv, .env, default-env.json, ./node_modules/**
120
- let blockList = "\\.cds$|\\.csv$|\\.hdbtabledata$"
121
- blockList += this.hasBuildOption(CONTENT_ENV, false) ? "|\\.env($|\\..*$)" : ""
122
- blockList += this.hasBuildOption(CONTENT_DEFAULT_ENV_JSON, false) ? "|default-env\\.json$" : ""
123
- blockList = new RegExp(blockList)
124
-
125
- await this.copyNativeContent(src, dest, (entry) => {
126
- if (entry.startsWith(dbSrc)) {
127
- // entire native content
128
- return true
129
- }
130
- if (fs.statSync(entry).isDirectory()) {
131
- return !/(\/|\\)node_modules(\/|\\)?$/.test(entry) || this.hasBuildOption(CONTENT_NODE_MODULES, true)
132
- }
133
- return !blockList.test(entry)
134
- })
119
+ // 2. staging build: copy files except *.cds, .env, default-env.json, ./node_modules/**
120
+ if (this.isStagingBuild()) {
121
+ let blockList = "\\.cds$|\\.csv$|\\.hdbtabledata$"
122
+ blockList += this.hasBuildOption(CONTENT_ENV, false) ? "|\\.env($|\\..*$)" : ""
123
+ blockList += this.hasBuildOption(CONTENT_DEFAULT_ENV_JSON, false) ? "|default-env\\.json$" : ""
124
+ blockList = new RegExp(blockList)
125
+
126
+ await this.copyNativeContent(src, dest, (entry) => {
127
+ if (entry.startsWith(dbSrc)) {
128
+ // entire native content
129
+ return true
130
+ }
131
+ if (fs.statSync(entry).isDirectory()) {
132
+ return !/(\/|\\)node_modules(\/|\\)?$/.test(entry) || this.hasBuildOption(CONTENT_NODE_MODULES, true)
133
+ }
134
+ return !blockList.test(entry)
135
+ })
136
+ }
135
137
  }
136
138
 
137
139
  /**
@@ -145,15 +147,20 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
145
147
  const dbCsvDir = path.join(src, "csv")
146
148
  const dbDataDir = path.join(src, "data")
147
149
  const csvDirs = [dbCsvDir, dbDataDir]
148
- const regex = RegExp('\\.cds$|\\.csv$|\\.hdbtabledata$')
149
150
  const regexData = RegExp('\\.csv$|\\.hdbtabledata$')
150
- await this.copyNativeContent(src, dest, (entry) => {
151
- if (fs.statSync(entry).isDirectory()) {
152
- return !/(\/|\\)node_modules(\/|\\)?$/.test(entry)
153
- }
154
- return (!regex.test(entry) && entry !== this.env.build.outputfile) ||
155
- (regexData.test(entry) && !entry.startsWith(dbCsvDir) && !entry.startsWith(dbDataDir))
156
- })
151
+
152
+ if (this.isStagingBuild()) {
153
+ const regex = RegExp('\\.cds$|\\.csv$|\\.hdbtabledata$')
154
+
155
+ await this.copyNativeContent(src, dest, (entry) => {
156
+ if (fs.statSync(entry).isDirectory()) {
157
+ return !/(\/|\\)node_modules(\/|\\)?$/.test(entry)
158
+ }
159
+ return (!regex.test(entry) && entry !== this.env.build.outputfile) ||
160
+ (regexData.test(entry) && !entry.startsWith(dbCsvDir) && !entry.startsWith(dbDataDir))
161
+ })
162
+ }
163
+
157
164
  // handle *.csv and *.hdbtabledata located in '<dbSrc>/data' and '<dbSrc>/csv' folder
158
165
  const allFiles = csvDirs.reduce((acc, csvDir) => {
159
166
  return acc.concat(BuildTaskHandlerInternal._find(csvDir, (entry) => {
@@ -244,7 +251,7 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
244
251
  const pluginTypes = new Set([...REQUIRED_PLUGIN_TYPES, ...undeployTypes])
245
252
 
246
253
  // compile to old format (.hdbcds) or new format (.hdbtable / .hdbview)
247
- const format = this.getBuildOption(DEPLOY_FORMAT) || this.env.get("requires.db." + DEPLOY_FORMAT) || this.env.get("hana." + DEPLOY_FORMAT)
254
+ const format = this.getBuildOption(DEPLOY_FORMAT) || this.env.requires.db?.[DEPLOY_FORMAT] || this.env.hana?.[DEPLOY_FORMAT]
248
255
  if (!this.cds.compile.to[format]) {
249
256
  return Promise.reject(new Error(`Invalid deploy-format defined: ${format}`))
250
257
  }
@@ -4,7 +4,7 @@ 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, DEFAULT_CSN_FILE_NAME } = require('../../constants')
7
+ const { BUILD_OPTION_OUTPUT_MODE, 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')
@@ -19,15 +19,15 @@ class JavaModuleBuilder extends BuildTaskHandlerEdmx {
19
19
  const { src, dest } = this.task
20
20
 
21
21
  const odataOptions = {
22
- version: this.env.get(ODATA_VERSION)
22
+ version: this.env.odata?.version
23
23
  }
24
24
 
25
25
  if (await isOldJavaStack([src, this.buildOptions.root])) {
26
- if (!this._isCompilerV1() && !this.env.get(`build.${SKIP_ASSERT_COMPILER_V2}`)) {
26
+ if (!this._isCompilerV1() && !this.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).get(ODATA_VERSION)) {
30
+ if (!this.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
  }
@@ -53,7 +53,7 @@ class JavaModuleBuilder extends BuildTaskHandlerEdmx {
53
53
  this._result.languageBundles = i18n.bundles
54
54
  }
55
55
  }
56
- if (!this.hasBuildOption(BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY)) {
56
+ if (this.isStagingBuild() && !this.hasBuildOption(BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY)) {
57
57
  await this._copyNativeContent(src, dest)
58
58
  }
59
59
  return this._result
@@ -61,8 +61,7 @@ class JavaModuleBuilder extends BuildTaskHandlerEdmx {
61
61
 
62
62
  async clean() {
63
63
  if (this.isStagingBuild()) {
64
- await super.clean()
65
- return
64
+ return super.clean()
66
65
  }
67
66
  this.logger._debug && this.logger.debug(`Deleting build target folder ${this.task.options.compileDest}`)
68
67
  await fs.promises.rm(this.task.options.compileDest, { force: true, recursive: true })
@@ -7,7 +7,7 @@ const { isStreamlinedMtx } = require('../../util')
7
7
  const ResourcesTarProvider = require('./resourcesTarBuilder')
8
8
 
9
9
  const { BUILD_TASK_HANA, FOLDER_GEN } = require('../../constants')
10
- const { WARNING } = require('../../buildTaskHandler')
10
+ const { WARNING, ERROR } = BuildTaskHandlerEdmx
11
11
  const FOLDER_SDC = "sdc"
12
12
  const FOLDER_NODE_MODULES = "node_modules"
13
13
  const FOLDER_TEMPLATES = "tpl"
@@ -42,7 +42,7 @@ class StreamlinedMtxBuilder {
42
42
  async build() {
43
43
  // Non-sidecar scenario: model-provider is implemented by the Nodejs app itself (default for Nodejs).
44
44
  // Only create resources TAR, compiled CSN(s) are created by the Nodejs app build task.
45
- const dest = this._handler.env.get("build.target") !== "." ? this._handler.task.dest : path.join(this._handler.task.dest, FOLDER_GEN)
45
+ const dest = this._handler.env.build?.target !== "." ? this._handler.task.dest : path.join(this._handler.task.dest, FOLDER_GEN)
46
46
  const model = await this._handler.model()
47
47
  if (!model) {
48
48
  return
@@ -64,7 +64,7 @@ class ClassicMtxBuilder {
64
64
  }
65
65
 
66
66
  async build() {
67
- if (this._handler.env.get("requires.toggles") === true) {
67
+ if (this._handler.env.requires.toggles === true) {
68
68
  throw new Error("Feature toggles are only supported by Streamlined MTX")
69
69
  }
70
70
 
@@ -78,7 +78,7 @@ class ClassicMtxBuilder {
78
78
  // custom build tasks for srv and db modules might be defined
79
79
  const tenantDbPath = await this._getHanaTenantDbPath()
80
80
 
81
- // validate extension whitlists defined for this SaaS application
81
+ // validate extension whitelists defined for this SaaS application
82
82
  this._validateExtensionAllowLists(model, tenantDbPath)
83
83
 
84
84
  // copy base model sources
@@ -100,7 +100,7 @@ class ClassicMtxBuilder {
100
100
  if (i18n) {
101
101
  this._handler._result.languageBundles = i18n.bundles
102
102
  }
103
-
103
+
104
104
  // copy native hana content and templates
105
105
  await this._copyNativeContent(this._handler.task.src, destSdc, tenantDbPath)
106
106
  }
@@ -114,7 +114,7 @@ class ClassicMtxBuilder {
114
114
  }
115
115
 
116
116
  async _copyNativeContent(src, dest, tenantDbPath) {
117
- // copying tmplates
117
+ // copying templates
118
118
  const tplSrc = path.join(src, FOLDER_TEMPLATES)
119
119
  if (fs.existsSync(tplSrc)) {
120
120
  const tplDest = path.join(path.dirname(dest), FOLDER_TEMPLATES)
@@ -122,9 +122,13 @@ class ClassicMtxBuilder {
122
122
  }
123
123
 
124
124
  if (tenantDbPath) {
125
- // copy any static HANA artifacts, e.g. .csv files
125
+ // copy native HANA artifacts, e.g. .csv files
126
126
  const dbDest = path.resolve(dest, path.relative(this._handler.buildOptions.root, tenantDbPath))
127
- await this._copyNativeHanaContent(tenantDbPath, dbDest)
127
+ if (!src.startsWith(dbDest)) { // ensure valid paths
128
+ await this._copyNativeHanaContent(tenantDbPath, dbDest)
129
+ } else {
130
+ this.pushMessage(`Invalid mtx build task configuration detected, destination folder ${dbDest} must not be nested within 'src' folder`, ERROR)
131
+ }
128
132
  }
129
133
  }
130
134
 
@@ -158,18 +162,16 @@ class ClassicMtxBuilder {
158
162
  * @param {Object} model - the reflected csn for this SaaS application
159
163
  */
160
164
  _validateExtensionAllowLists(model, tenantDbPath) {
161
- let entityWhitelist = this._handler.env.get("mtx.entity-whitelist")
162
- let serviceWhitelist = this._handler.env.get("mtx.service-whitelist")
163
- let extensionAllowlist = this._handler.env.get("mtx.extension-allowlist")
165
+ let entityWhitelist = this._handler.env.mtx?.["entity-whitelist"]
166
+ let serviceWhitelist = this._handler.env.mtx?.["service-whitelist"]
167
+ let extensionAllowlist = this._handler.env.mtx?.["extension-allowlist"]
164
168
 
165
169
  // for java projects mtx configuration is part of sidecar config
166
170
  if (!entityWhitelist && !serviceWhitelist && !extensionAllowlist && tenantDbPath) {
167
171
  const dbEnv = this._handler.env.for("cds", tenantDbPath)
168
- if (dbEnv && dbEnv.get("mtx")) {
169
- entityWhitelist = dbEnv.get("mtx.entity-whitelist")
170
- serviceWhitelist = dbEnv.get("mtx.service-whitelist")
171
- extensionAllowlist = dbEnv.get("mtx.extension-allowlist")
172
- }
172
+ entityWhitelist = dbEnv.mtx?.["entity-whitelist"]
173
+ serviceWhitelist = dbEnv.mtx?.["service-whitelist"]
174
+ extensionAllowlist = dbEnv.mtx?.["extension-allowlist"]
173
175
  }
174
176
 
175
177
  function isValid(e, pattern, nsPattern, kind) {
@@ -231,14 +233,14 @@ class ClassicMtxBuilder {
231
233
  // ensure that the hana task is contained even if this mtx task has been executed solely using "cds build --for mtx"
232
234
  if (!tasks.find(task => task.for === BUILD_TASK_HANA)) {
233
235
  //mtx task might have been executed as separate task
234
- tasks = this._handler.env.get("build.tasks") || []
236
+ tasks = this._handler.env.build?.tasks || []
235
237
  if (tasks.length === 0) {
236
238
  const provider = new BuildTaskProviderInternal(this._handler.cds, this._handler.logger)
237
239
  await provider.lookupTasks(tasks, this._handler.buildOptions)
238
240
  }
239
241
  }
240
242
  // the SaaS app might use a tenant aware db as well as a shared db deployed using static hdi-deployer
241
- // pick the hana build task refering to the tenant aware db - the src path has to be contained in this build task's model options
243
+ // 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
242
244
  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))
243
245
  let tenantDbPath = hanaDbPaths.find(hanaDbPath => hanaDbPaths.length === 1 || modelPaths.some(modelPath => path.dirname(modelPath) === hanaDbPath))
244
246
 
@@ -22,12 +22,9 @@ class ResourcesTarBuilder {
22
22
  await this.writeTarFile(files, root, path.join(dest, DEFAULT_TAR_NAME))
23
23
  }
24
24
 
25
- async writeTarFile(files, root, tarFile) {
26
- const { packTarArchive } = require('../../../../lib/utils/resources')
27
- const buffer = await packTarArchive(files, root)
28
- if (buffer) {
29
- await this.handler.write(buffer).to(tarFile)
30
- }
25
+ async writeTarFile(files, root, tarFile) { // REVISIT: eliminate this anti-pattern helper
26
+ 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
31
28
  }
32
29
 
33
30
  async _getResources(model) {
@@ -45,10 +42,10 @@ class ResourcesTarBuilder {
45
42
 
46
43
  /**
47
44
  * Reads all resources for HANA deployment - 'db/src/**' and 'db/undeploy.json'.
48
- * - CSV files, native HANA artefacts, generated HANA artefacts, undeploy.json
45
+ * - CSV files, native HANA artifacts, generated HANA artifacts, undeploy.json
49
46
  * See '../../../../lib.deploy'
50
47
  * Note: the hana build task has already been executed
51
- * @param {string} root
48
+ * @param {string} root
52
49
  * @returns an array containing all resources
53
50
  */
54
51
  _getHanaResources(root) {
@@ -65,9 +62,9 @@ class ResourcesTarBuilder {
65
62
 
66
63
  /**
67
64
  * Reads all resources for Sqlite deployment - see '../../../../lib.deploy'
68
- *
69
- * @param {object} model
70
- * @returns
65
+ *
66
+ * @param {object} model
67
+ * @returns
71
68
  */
72
69
  async _getSqliteResources(model) {
73
70
  const { resources } = this.handler.cds.deploy
@@ -4,7 +4,7 @@ const { FOLDER_GEN, DEFAULT_CSN_FILE_NAME } = require('../../constants')
4
4
  const NodeCfModuleBuilder = require('../nodejs')
5
5
  const ResourcesTarProvider = require('../mtx/resourcesTarBuilder')
6
6
  const { INFO, ERROR } = NodeCfModuleBuilder
7
- const { relativePaths, BuildError } = require('../../util')
7
+ const { relativePaths, BuildError, resolveRequiredSapModels } = require('../../util')
8
8
 
9
9
  const DEFAULT_MAIN_FOLDER = "_main"
10
10
 
@@ -47,7 +47,7 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
47
47
  const model = this._compileSidecarSync(sidecarEnv)
48
48
  await this.compileToJson(model, path.join(destSidecarSrc, DEFAULT_CSN_FILE_NAME))
49
49
  await this.collectLanguageBundles(model, destSidecarSrc)
50
- await this._copyProjectRootContent(this.task.src, destSidecar)
50
+ await this.copyProjectRootContent(this.task.src, destSidecar)
51
51
  }
52
52
 
53
53
  /**
@@ -56,15 +56,15 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
56
56
  */
57
57
  async _buildMainApp(sidecarEnv) {
58
58
  if (sidecarEnv.requires['cds.xt.ModelProviderService']?.kind !== 'in-sidecar') {
59
- throw new Error("Invalid MTX sidecar configuration - \"cds.xt.ModelProviderService\": \"in-sidecar\" missing")
59
+ throw new BuildError("CDS build failed", "Invalid MTX sidecar configuration - \"cds.xt.ModelProviderService\": \"in-sidecar\" missing.")
60
60
  }
61
61
  let main = sidecarEnv.requires['cds.xt.ModelProviderService']?.root
62
- const profiles = this.env.get("profiles") || []
62
+ const profiles = this.env.profiles || []
63
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
67
- this.pushMessage(`Sidecar main application build results are created in folder '${main}'. Enable the 'production' or 'prod' profile if the folder is configured differently.`, INFO)
67
+ this.pushMessage(`MTX sidecar build results are created in folder '${main}'. Enable 'production' or 'prod' profile if the folder is configured differently.`, INFO)
68
68
  }
69
69
  const destRoot = this.task.dest
70
70
  const destMain = path.join(destRoot, main)
@@ -98,21 +98,17 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
98
98
  const modelFilePaths = this.cds.resolve(modelPaths)
99
99
 
100
100
  if (!modelFilePaths || modelFilePaths.length === 0) {
101
- throw new BuildError("No model found for MTX sidecar app")
102
- }
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)
101
+ throw new BuildError("No CDS service models found in MTX sidecar. Make sure required npm modules are installed.")
113
102
  }
114
103
  this._logger._debug && this._logger.debug(`sidecar model: ${relativePaths(this.buildOptions.root, modelFilePaths).join(", ")}`)
115
104
 
105
+ // check whether all models belonging to the @sap namespace can be resolved
106
+ const unresolved = resolveRequiredSapModels(this.cds, modelPaths)
107
+ if (unresolved.length > 0) {
108
+ // log error, but don't fail
109
+ this.pushMessage(`CDS service models [${unresolved.join(', ')}] required by MTX sidecar cannot be resolved. Make sure to install the missing npm modules.`, ERROR)
110
+ }
111
+
116
112
  // synchronous compilation
117
113
  return this.cds.load(modelFilePaths, { sync: true, ...this.options() })
118
114
  } finally {
@@ -2,7 +2,7 @@ const fs = require('fs')
2
2
  const path = require('path')
3
3
  const BuildTaskHandlerEdmx = require('../buildTaskHandlerEdmx')
4
4
  const { BuildError } = require('../../util')
5
- const { BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY, ODATA_VERSION, ODATA_VERSION_V2, FOLDER_GEN, BUILD_NODEJS_EDMX_GENERAION, EDMX_GENERATION,
5
+ const { BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY, ODATA_VERSION_V2, FOLDER_GEN, BUILD_NODEJS_EDMX_GENERAION, EDMX_GENERATION,
6
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
7
  const { WARNING, ERROR } = BuildTaskHandlerEdmx
8
8
 
@@ -29,7 +29,7 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
29
29
  }
30
30
 
31
31
  options() {
32
- const options = super.options(), {cds} = this
32
+ const options = super.options(), { cds } = this
33
33
  if (cds.requires.extensibility || cds.requires.toggles) options.flavor = 'xtended'
34
34
  return options
35
35
  }
@@ -38,7 +38,7 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
38
38
  const destSrv = this.isStagingBuild() ? this.destSrv : path.resolve(this.destSrv, this.env.folders.srv)
39
39
  const destRoot = this.isStagingBuild() ? this.task.dest : this.destSrv
40
40
 
41
- if (this.env.get(ODATA_VERSION) === ODATA_VERSION_V2) {
41
+ if (this.env.odata?.version === ODATA_VERSION_V2) {
42
42
  // log warning as nodejs is only supporting odata version V4
43
43
  this.pushMessage("OData v2 is not supported by node runtime. Make sure to define OData v2 in cds configuration.", WARNING)
44
44
  }
@@ -58,7 +58,7 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
58
58
  await this.compileToEdmx(m, this.destSrv)
59
59
  }
60
60
 
61
- if (!this.hasBuildOption(BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY)) {
61
+ if (this.isStagingBuild() && !this.hasBuildOption(BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY)) {
62
62
  const srcSrv = this.task.src === this.buildOptions.root ? path.resolve(this.task.src, this.env.folders.srv) : this.task.src
63
63
  await this._copyNativeContent(this.buildOptions.root, srcSrv, destRoot, destSrv)
64
64
  }
@@ -80,11 +80,11 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
80
80
  const filesFilter = await this._copySrvContent(srcSrv, destRoot, destSrv)
81
81
 
82
82
  // project/* -> 'gen/srv/*'
83
- await this._copyProjectRootContent(srcRoot, destRoot, (entry) => !filesFilter.includes(path.basename(entry)))
83
+ await this.copyProjectRootContent(srcRoot, destRoot, (entry) => !filesFilter.includes(path.basename(entry)))
84
84
  }
85
85
 
86
86
  /**
87
- * 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 for nodejs staging builds from the given <em>src</em>' folder (e.g. 'project/srv') to either <em>destRoot</em> (e.g. 'project/gen/srv')
88
88
  * or <em>destSrv</em> (e.g. 'project/gen/srv/srv') folders according to the file semantics.
89
89
  * Files with project semantics like 'package.json' or '.npmrc' file are copied to <em>destRoot</em> while others like '.js' service handlers
90
90
  * are copied to <em>destSrv</em>.
@@ -127,6 +127,7 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
127
127
  return this.copy(path.join(src, fileName)).to(path.join(destRoot, fileName))
128
128
  }
129
129
  }))
130
+
130
131
  return srvRootFileNames
131
132
  }
132
133
 
@@ -137,7 +138,7 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
137
138
  * @param {*} dest
138
139
  * @param {*} filter - copy file if filter function returns true
139
140
  */
140
- async _copyProjectRootContent(src, dest, filter) {
141
+ async copyProjectRootContent(src, dest, filter) {
141
142
  let { folders = ['i18n'] } = this.env.i18n
142
143
  folders.push('handlers')
143
144
  folders = folders.map(folder => path.join(src, folder))
package/bin/build/util.js CHANGED
@@ -127,17 +127,34 @@ function isStreamlinedMtx(cds) {
127
127
  if (hasOptionValue(process.env.OLD_MTX, true)) {
128
128
  return false
129
129
  }
130
- return (cds.env.get("requires.toggles")
130
+ return (cds.env.requires.toggles
131
131
  || cds.env.requires['cds.xt.ModelProviderService']
132
- || (typeof cds.env.get("requires.multitenancy") === "object")
132
+ || (typeof cds.env.requires.multitenancy === "object")
133
133
  ) && !(() => { try { return cds.mtx } catch (e) { /**/ } })()
134
134
  }
135
135
 
136
+ /**
137
+ * Returns a list of fully qualified model names belonging to the '@sap' namespace that cannot be resolved.
138
+ * E.g. the npm module might NOT be installed.
139
+ * @param {*} cds
140
+ * @param {Array} modelPaths
141
+ * @returns {Array}
142
+ */
143
+ function resolveRequiredSapModels(cds, modelPaths) {
144
+ return modelPaths.filter(p => {
145
+ if (p.startsWith('@sap/')) {
146
+ const files = cds.resolve(p)
147
+ return !files || files.length === 0
148
+ }
149
+ })
150
+ }
151
+
136
152
  class BuildMessage extends Error {
137
- constructor(message, severity) {
153
+ constructor(message, severity = SEVERITY_ERROR) {
138
154
  super(message)
139
155
  this.name = "BuildMessage"
140
156
  this.severity = severity
157
+ this.stack = ""
141
158
  }
142
159
  toString() {
143
160
  return this.severity + ": " + this.message
@@ -148,7 +165,7 @@ class BuildError extends BuildMessage {
148
165
  constructor(message, messages = []) {
149
166
  super(message, SEVERITY_ERROR)
150
167
  this.name = "BuildError"
151
- this.messages = messages
168
+ this.messages = Array.isArray(messages) ? messages : [messages]
152
169
  }
153
170
 
154
171
  // for compatibility reasons
@@ -170,6 +187,7 @@ module.exports = {
170
187
  hasOptionValue,
171
188
  relativePaths,
172
189
  isStreamlinedMtx,
190
+ resolveRequiredSapModels,
173
191
  BuildMessage,
174
192
  BuildError
175
193
  }
package/bin/cds.js CHANGED
@@ -8,10 +8,6 @@ const cli = { //NOSONAR
8
8
  },
9
9
 
10
10
  exec (cmd = process.argv[2], ...argv) {
11
- if (process[Symbol.for('ts-node.register.instance')]) {
12
- process.env.CDS_TYPESCRIPT = process.env.CDS_TYPESCRIPT || 'true'
13
- }
14
- require('util').inspect.defaultOptions = { colors: !!process.stderr.isTTY, depth:11 }
15
11
  if (!argv.length) argv = process.argv.slice(3)
16
12
  if (process.env.NODE_ENV !== 'test') this.errorHandlers()
17
13
  if (cmd in this.Shortcuts) cmd = process.argv[2] = this.Shortcuts[cmd]
@@ -102,6 +98,14 @@ const _die_needsDk = (cmd) => {
102
98
  return console.error (message)
103
99
  }
104
100
 
101
+ require('util').inspect.defaultOptions = {
102
+ colors: !!process.stdout.isTTY && !!process.stderr.isTTY,
103
+ depth:11, breakLength:111
104
+ }
105
+ if (process[Symbol.for('ts-node.register.instance')]) {
106
+ process.env.CDS_TYPESCRIPT = process.env.CDS_TYPESCRIPT || 'true'
107
+ }
108
+
105
109
  module.exports = Object.assign ((..._) => cli.exec(..._), cli)
106
110
  if (!module.parent) cli.exec()
107
111
  /* eslint no-console:off */
@@ -14,6 +14,11 @@ const CF_COMMAND = 'cf';
14
14
  const POLL_COUNTER = 40;
15
15
  const POLL_DELAY = 2500; //ms
16
16
 
17
+ const OPERATION_STATE_INITIAL = 'initial';
18
+ const OPERATION_STATE_IN_PROGRESS = 'in progress';
19
+ const OPERATION_STATE_FAILED = 'failed';
20
+ const OPERATION_STATE_SUCCEEDED = 'succeeded';
21
+
17
22
 
18
23
  class CfUtil {
19
24
 
@@ -94,7 +99,11 @@ class CfUtil {
94
99
  }
95
100
 
96
101
  const response = await this._cfRun(...args);
97
- const result = response ? JSON.parse(response) : {};
102
+ if (!response) {
103
+ throw new Error(`The response from the server was empty. Maybe your token is expired. Run the command again and re-log on in case the problem persists.`);
104
+ }
105
+
106
+ const result = JSON.parse(response);
98
107
  if (result.errors) {
99
108
  const errorMessage = result.errors.map((entry) => `${entry.title}: ${entry.detail} (${entry.code})`).join('\n');
100
109
  throw new Error(errorMessage);
@@ -104,10 +113,10 @@ class CfUtil {
104
113
 
105
114
  _extract(string, pattern, errorMsg) {
106
115
  const match = string.match(pattern);
107
- if (!match || !match[1]) {
108
- throw new Error(errorMsg);
116
+ if (match?.[1]) {
117
+ return match[1];
109
118
  }
110
- return match[1];
119
+ throw new Error(errorMsg);
111
120
  }
112
121
 
113
122
  async getCfTargetFromConfigFile() {
@@ -138,6 +147,8 @@ class CfUtil {
138
147
  }
139
148
 
140
149
  async getCfTarget() {
150
+ // check if token is valid or expired / missing
151
+ await this._cfRun('oauth-token');
141
152
  return await this.getCfTargetFromConfigFile() || await this.getCfTargetFromCli();
142
153
  }
143
154
 
@@ -149,13 +160,13 @@ class CfUtil {
149
160
 
150
161
  const { org, space } = target;
151
162
  const orgs = await this._cfRequest(`/v3/organizations`, { names: org });
152
- if (!orgs.resources || orgs.resources.length !== 1) {
163
+ if (!orgs?.resources?.length) {
153
164
  throw new Error(`CF org ${bold(org)} not found!`);
154
165
  }
155
166
 
156
167
  const orgGuid = orgs.resources[0].guid;
157
168
  const spaces = await this._cfRequest(`/v3/spaces`, { names: space, organization_guids: orgGuid });
158
- if (!spaces.resources || spaces.resources.length !== 1) {
169
+ if (!spaces?.resources?.length) {
159
170
  throw new Error(`CF space ${bold(space)} not found in org ${bold(org)}!`);
160
171
  }
161
172
 
@@ -179,16 +190,27 @@ class CfUtil {
179
190
  space_guids: spaceInfo.spaceGuid,
180
191
  organization_guids: spaceInfo.orgGuid
181
192
  });
182
- if (!serviceInstances || !serviceInstances.resources || serviceInstances.resources.length < 1) {
193
+ if (!serviceInstances?.resources?.length) {
183
194
  return null;
184
195
  }
185
196
 
186
197
  const serviceInstance = serviceInstances.resources[0];
187
- if (serviceInstance && serviceInstance.last_operation && serviceInstance.last_operation.state !== 'in progress') {
188
- return serviceInstance;
189
- }
198
+ switch (serviceInstance?.last_operation?.state?.toLowerCase()) {
199
+ case OPERATION_STATE_INITIAL:
200
+ case OPERATION_STATE_IN_PROGRESS:
201
+ await this._sleep(POLL_DELAY);
202
+ break;
203
+
204
+ case OPERATION_STATE_SUCCEEDED:
205
+ return serviceInstance;
206
+
207
+ case OPERATION_STATE_FAILED:
208
+ throw new Error(`The returned service reported state '${OPERATION_STATE_FAILED}'.\n${JSON.stringify(serviceInstance, null, 4)}`);
190
209
 
191
- await this._sleep(POLL_DELAY);
210
+ default:
211
+ LOG.log(`Unsupported server response state '${serviceInstance?.last_operation?.state}'. Waiting for next response.`);
212
+ break;
213
+ }
192
214
  }
193
215
 
194
216
  throw new Error(`Timeout occurred while getting service ${bold(serviceName)}`);
@@ -214,7 +236,7 @@ class CfUtil {
214
236
  service_offering_names: serviceOfferingName
215
237
  });
216
238
 
217
- if (!servicePlan.resources || servicePlan.resources.length === 0) {
239
+ if (!servicePlan?.resources?.length) {
218
240
  throw new Error(`No service plans found`);
219
241
  }
220
242
 
@@ -261,16 +283,29 @@ class CfUtil {
261
283
  while (counter > 0) {
262
284
  counter--;
263
285
  const bindings = await this._cfRequest(`/v3/service_credential_bindings`, { names: serviceKeyName, service_instance_guids: serviceInstance.guid });
264
- if (!bindings || !bindings.resources || bindings.resources.length < 1) {
286
+ if (!bindings?.resources?.length) {
265
287
  return null;
266
288
  }
289
+
267
290
  const binding = bindings.resources[0];
268
- if (binding && binding.last_operation && binding.last_operation.state !== 'in progress') {
269
- const keyDetails = await this._cfRequest(`/v3/service_credential_bindings/${encodeURIComponent(binding.guid)}/details`);
270
- return keyDetails.credentials;
271
- }
291
+ switch (binding?.last_operation?.state?.toLowerCase()) {
292
+ case OPERATION_STATE_INITIAL:
293
+ case OPERATION_STATE_IN_PROGRESS:
294
+ await this._sleep(POLL_DELAY);
295
+ break;
296
+
297
+ case OPERATION_STATE_SUCCEEDED: {
298
+ const keyDetails = await this._cfRequest(`/v3/service_credential_bindings/${encodeURIComponent(binding.guid)}/details`);
299
+ return keyDetails.credentials;
300
+ }
301
+
302
+ case OPERATION_STATE_FAILED:
303
+ throw new Error(`The returned binding reported state '${OPERATION_STATE_FAILED}'.\n${JSON.stringify(binding, null, 4)}`);
272
304
 
273
- await this._sleep(POLL_DELAY);
305
+ default:
306
+ LOG.log(`Unsupported server response state '${binding?.last_operation?.state}'. Waiting for next response.`);
307
+ break;
308
+ }
274
309
  }
275
310
 
276
311
  throw new Error(`Timeout occurred while getting service key ${bold(serviceKeyName)}`);