@sap/cds-dk 6.8.2 → 7.0.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 (218) hide show
  1. package/bin/activate.js +15 -7
  2. package/bin/add.js +29 -15
  3. package/bin/bind.js +49 -116
  4. package/bin/build.js +114 -0
  5. package/bin/cds.js +67 -24
  6. package/bin/compile/index.js +2 -2
  7. package/bin/deploy/index.js +46 -39
  8. package/bin/deploy/to-hana.js +11 -8
  9. package/bin/deploy/to-postgres.js +8 -2
  10. package/bin/deploy/to-sqlite.js +0 -1
  11. package/bin/env.js +75 -41
  12. package/bin/eval.js +3 -3
  13. package/bin/extend.js +17 -10
  14. package/bin/help.js +22 -21
  15. package/bin/import.js +10 -7
  16. package/bin/init.js +38 -17
  17. package/bin/login.js +69 -55
  18. package/bin/logout.js +12 -9
  19. package/bin/migrate.js +10 -4
  20. package/bin/pull.js +12 -5
  21. package/bin/push.js +24 -13
  22. package/bin/repl.js +1 -0
  23. package/bin/run.js +25 -0
  24. package/bin/subscribe.js +13 -8
  25. package/bin/unsubscribe.js +15 -8
  26. package/bin/upgrade.js +15 -9
  27. package/bin/util/log.js +24 -0
  28. package/bin/version.js +124 -0
  29. package/bin/watch.js +1 -35
  30. package/bin/watched.js +39 -0
  31. package/lib/bind/KubernetesApiAccessor.js +2 -2
  32. package/lib/bind/bindingManager.js +1 -1
  33. package/lib/bind/bindingUtil.js +0 -9
  34. package/lib/bind/cfKeyProvider.js +10 -72
  35. package/lib/bind/index.js +114 -0
  36. package/lib/bind/kubernetesCredentialsProvider.js +2 -2
  37. package/lib/bind/userCdsrcJsonStorageProvider.js +7 -44
  38. package/lib/build/buildTaskEngine.js +355 -0
  39. package/lib/build/buildTaskFactory.js +284 -0
  40. package/lib/build/buildTaskHandler.js +240 -0
  41. package/lib/build/buildTaskProvider.js +22 -0
  42. package/lib/build/buildTaskProviderFactory.js +182 -0
  43. package/lib/build/constants.js +56 -0
  44. package/lib/build/index.js +21 -0
  45. package/lib/build/provider/buildTaskHandlerEdmx.js +83 -0
  46. package/lib/build/provider/buildTaskHandlerFeatureToggles.js +132 -0
  47. package/lib/build/provider/buildTaskHandlerInternal.js +228 -0
  48. package/lib/build/provider/buildTaskProviderInternal.js +370 -0
  49. package/lib/build/provider/fiori/index.js +167 -0
  50. package/lib/build/provider/hana/2migration.js +179 -0
  51. package/lib/build/provider/hana/index.js +522 -0
  52. package/lib/build/provider/hana/migrationtable.js +472 -0
  53. package/lib/build/provider/hana/template/.hdiconfig-all +163 -0
  54. package/lib/build/provider/hana/template/.hdiconfig-cloud +137 -0
  55. package/lib/build/provider/hana/template/.hdinamespace +4 -0
  56. package/lib/build/provider/hana/template/package.json +12 -0
  57. package/lib/build/provider/hana/template/undeploy.json +5 -0
  58. package/lib/build/provider/java/index.js +93 -0
  59. package/lib/build/provider/java-cf/index.js +1 -0
  60. package/lib/build/provider/mtx/index.js +37 -0
  61. package/lib/build/provider/mtx/resourcesTarBuilder.js +94 -0
  62. package/lib/build/provider/mtx-extension/index.js +131 -0
  63. package/lib/build/provider/mtx-sidecar/index.js +138 -0
  64. package/lib/build/provider/node-cf/index.js +1 -0
  65. package/lib/build/provider/nodejs/index.js +178 -0
  66. package/lib/build/util.js +263 -0
  67. package/lib/cds.js +9 -21
  68. package/lib/client/auth_manager.js +39 -16
  69. package/lib/client/helper/dependency_helper.js +7 -6
  70. package/lib/client/push.js +2 -1
  71. package/lib/client/settings_manager.js +10 -39
  72. package/lib/deploy/to-hana/hana.js +273 -0
  73. package/lib/deploy/to-hana/hdiDeployUtil.js +68 -0
  74. package/lib/deploy/to-hana/mtaUtil.js +171 -0
  75. package/lib/import/asyncapi/importAsyncAPI.js +3 -2
  76. package/lib/import/asyncapi/index.js +5 -2
  77. package/lib/import/asyncapi/parserContext.js +67 -26
  78. package/lib/import/asyncapi/utils.js +34 -3
  79. package/lib/import/common.js +40 -30
  80. package/lib/import/importapi.js +36 -35
  81. package/lib/index.js +12 -17
  82. package/lib/init/constants.js +36 -71
  83. package/lib/init/index.js +18 -52
  84. package/lib/init/template/_merging/registry-mta.js +21 -3
  85. package/lib/init/template/approuter/files/mta.yaml.hbs +29 -29
  86. package/lib/init/template/approuter/files/values.yaml.hbs +5 -5
  87. package/lib/init/template/approuter/files/xs-app.json.hbs +8 -2
  88. package/lib/init/template/approuter/index.js +7 -8
  89. package/lib/init/template/auditlog/files/mta.yaml.hbs +5 -5
  90. package/lib/init/template/auditlog/files/package.json.hbs +13 -13
  91. package/lib/init/template/auditlog/index.js +3 -3
  92. package/lib/init/template/cf-manifest/files/manifest.yml.hbs +22 -22
  93. package/lib/init/template/cf-manifest/files/services-manifest.yml.hbs +3 -3
  94. package/lib/init/template/connectivity/files/mta.yaml.hbs +14 -0
  95. package/lib/init/template/connectivity/files/package.json.hbs +17 -0
  96. package/lib/init/template/connectivity/index.js +21 -4
  97. package/lib/init/template/data/index.js +5 -1
  98. package/lib/init/template/destination/files/mta.yaml.hbs +14 -0
  99. package/lib/init/template/destination/files/package.json.hbs +17 -0
  100. package/lib/init/template/{destinations → destination}/index.js +19 -13
  101. package/lib/init/template/enterprise-messaging/files/event-mesh.json.hbs +3 -3
  102. package/lib/init/template/enterprise-messaging/files/mta.yaml.hbs +5 -5
  103. package/lib/init/template/enterprise-messaging/files/package.json.hbs +9 -9
  104. package/lib/init/template/enterprise-messaging/files/xs-security.json.hbs +19 -0
  105. package/lib/init/template/enterprise-messaging/index.js +27 -6
  106. package/lib/init/template/enterprise-messaging-shared/files/event-mesh.json.hbs +3 -3
  107. package/lib/init/template/enterprise-messaging-shared/files/mta.yaml.hbs +5 -5
  108. package/lib/init/template/enterprise-messaging-shared/files/package.json.hbs +9 -9
  109. package/lib/init/template/enterprise-messaging-shared/index.js +4 -5
  110. package/lib/init/template/extensibility/files/package.json.hbs +10 -10
  111. package/lib/init/template/extensibility/index.js +8 -9
  112. package/lib/init/template/file-based-messaging/files/package.json.hbs +9 -9
  113. package/lib/init/template/file-based-messaging/index.js +3 -4
  114. package/lib/init/template/hana/files/db/package.json +1 -1
  115. package/lib/init/template/hana/files/mta.yaml.hbs +21 -21
  116. package/lib/init/template/hana/files/package.json.hbs +16 -22
  117. package/lib/init/template/hana/files/values.yaml.hbs +1 -1
  118. package/lib/init/template/hana/index.js +5 -15
  119. package/lib/init/template/helm/files/Chart.yaml.hbs +4 -4
  120. package/lib/init/template/helm/files/values.yaml.hbs +6 -6
  121. package/lib/init/template/helm/index.js +5 -10
  122. package/lib/init/template/helm/subcharts/content-deployment/Chart.yaml +2 -0
  123. package/lib/init/template/helm/subcharts/content-deployment/templates/_helpers.tpl +37 -31
  124. package/lib/init/template/helm/subcharts/content-deployment/templates/job.yaml +13 -14
  125. package/lib/init/template/helm/subcharts/content-deployment/templates/secret.yaml +2 -2
  126. package/lib/init/template/helm/subcharts/content-deployment/templates/service-account.yaml +1 -1
  127. package/lib/init/template/helm/subcharts/content-deployment/templates/service-binding.yaml +3 -3
  128. package/lib/init/template/helm/subcharts/content-deployment/values.md +116 -116
  129. package/lib/init/template/helm/subcharts/service-instance/Chart.yaml +2 -0
  130. package/lib/init/template/helm/subcharts/service-instance/templates/_helpers.tpl +9 -9
  131. package/lib/init/template/helm/subcharts/service-instance/templates/serviceinstance.yaml +2 -2
  132. package/lib/init/template/helm/subcharts/service-instance/values.md +15 -15
  133. package/lib/init/template/helm/subcharts/web-application/Chart.yaml +2 -0
  134. package/lib/init/template/helm/subcharts/web-application/templates/NOTES.txt +15 -0
  135. package/lib/init/template/helm/subcharts/web-application/templates/_helpers.tpl +23 -23
  136. package/lib/init/template/helm/subcharts/web-application/templates/api-rule.yaml +4 -4
  137. package/lib/init/template/helm/subcharts/web-application/templates/deployment.yaml +17 -17
  138. package/lib/init/template/helm/subcharts/web-application/templates/network-policy.yaml +24 -3
  139. package/lib/init/template/helm/subcharts/web-application/templates/pod-disruption-budget.yaml +3 -3
  140. package/lib/init/template/helm/subcharts/web-application/templates/secret.yaml +2 -2
  141. package/lib/init/template/helm/subcharts/web-application/templates/service-account.yaml +1 -1
  142. package/lib/init/template/helm/subcharts/web-application/templates/service-binding.yaml +3 -3
  143. package/lib/init/template/helm/subcharts/web-application/templates/service.yaml +3 -3
  144. package/lib/init/template/helm/subcharts/web-application/values.md +141 -123
  145. package/lib/init/template/helm/subcharts/web-application/values.schema.json +7 -0
  146. package/lib/init/template/helm/subcharts/web-application/values.yaml +1 -0
  147. package/lib/init/template/html5-repo/index.js +4 -5
  148. package/lib/init/template/java/index.js +5 -5
  149. package/lib/init/template/kibana-logging/files/mta.yaml.hbs +5 -5
  150. package/lib/init/template/kibana-logging/files/package.json.hbs +9 -9
  151. package/lib/init/template/kibana-logging/index.js +3 -4
  152. package/lib/init/template/lint/index.js +3 -3
  153. package/lib/init/template/mta/files/mta.yaml.hbs +14 -14
  154. package/lib/init/template/mta/files/package.json +5 -0
  155. package/lib/init/template/mta/index.js +12 -13
  156. package/lib/init/template/mtx/index.js +2 -2
  157. package/lib/init/template/multitenancy/files/mta.yaml.hbs +29 -27
  158. package/lib/init/template/multitenancy/files/package.json.hbs +17 -14
  159. package/lib/init/template/multitenancy/files/package.sidecar.json.hbs +7 -7
  160. package/lib/init/template/multitenancy/files/values.yaml.hbs +19 -19
  161. package/lib/init/template/multitenancy/index.js +14 -16
  162. package/lib/init/template/nodejs/files/package.json.hbs +1 -1
  163. package/lib/init/template/nodejs/index.js +5 -8
  164. package/lib/init/template/redis-messaging/files/mta.yaml.hbs +5 -5
  165. package/lib/init/template/redis-messaging/files/package.json.hbs +9 -9
  166. package/lib/init/template/redis-messaging/index.js +4 -5
  167. package/lib/init/template/sample/files/app/_i18n/i18n.properties +43 -0
  168. package/lib/init/template/sample/files/app/_i18n/i18n_de.properties +14 -0
  169. package/lib/init/template/sample/files/app/admin-books/fiori-service.cds +114 -0
  170. package/lib/init/template/sample/files/app/admin-books/webapp/Component.js +8 -0
  171. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n.properties +11 -0
  172. package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +129 -0
  173. package/lib/init/template/sample/files/app/appconfig/fioriSandboxConfig.json +57 -0
  174. package/lib/init/template/sample/files/app/common.cds +263 -0
  175. package/lib/init/template/sample/files/app/index.html +30 -0
  176. package/lib/init/template/sample/files/app/services.cds +5 -0
  177. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Authors.csv +5 -0
  178. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Books.csv +6 -0
  179. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Books_texts.csv +5 -0
  180. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Genres.csv +16 -0
  181. package/lib/init/template/sample/files/db/schema.cds +31 -0
  182. package/lib/init/template/sample/files/nodejs/admin-service.js +12 -0
  183. package/lib/init/template/sample/files/nodejs/cat-service.js +26 -0
  184. package/lib/init/template/sample/files/srv/admin-service.cds +5 -0
  185. package/lib/init/template/sample/files/srv/cat-service.cds +16 -0
  186. package/lib/init/template/sample/index.js +24 -0
  187. package/lib/init/template/sample-tiny/index.js +39 -0
  188. package/lib/init/template/samples/index.js +5 -56
  189. package/lib/init/template/sqlite/files/package.json.hbs +13 -13
  190. package/lib/init/template/sqlite/index.js +2 -2
  191. package/lib/init/template/templateBase.js +24 -1
  192. package/lib/init/template/toggles/files/package.json.hbs +10 -10
  193. package/lib/init/template/toggles/index.js +3 -3
  194. package/lib/init/template/typer/files/gitignore +1 -0
  195. package/lib/init/template/typer/files/package.json +8 -0
  196. package/lib/init/template/typer/files/xsconfig.json +9 -0
  197. package/lib/init/template/typer/index.js +36 -0
  198. package/lib/init/template/xsuaa/files/mta.yaml.hbs +12 -12
  199. package/lib/init/template/xsuaa/files/package.json.hbs +13 -13
  200. package/lib/init/template/xsuaa/files/values.yaml.hbs +8 -8
  201. package/lib/init/template/xsuaa/index.js +8 -19
  202. package/lib/init/util/merge.js +73 -59
  203. package/lib/init/util/mvn.js +21 -17
  204. package/lib/init/util/projectReader.js +49 -87
  205. package/lib/init/util/xml.js +14 -23
  206. package/lib/util/cfUtil.js +366 -0
  207. package/lib/util/command.js +3 -3
  208. package/lib/util/gitUtil.js +55 -0
  209. package/lib/util/version.js +27 -0
  210. package/npm-shrinkwrap.json +339 -277
  211. package/package.json +6 -6
  212. package/CHANGELOG.md +0 -1453
  213. package/bin/build/all.js +0 -88
  214. package/bin/build/index.js +0 -82
  215. package/lib/compile/index.js +0 -14
  216. /package/lib/init/template/{samples → sample-tiny}/files/nodejs/db/data/my.bookshop-Books.csv +0 -0
  217. /package/lib/init/template/{samples → sample-tiny}/files/nodejs/db/data-model.cds +0 -0
  218. /package/lib/init/template/{samples → sample-tiny}/files/nodejs/srv/cat-service.cds.hbs +0 -0
@@ -0,0 +1,355 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const cds = require('../cds'), { _log: log } = cds
4
+ const { sortMessagesSeverityAware, deduplicateMessages, CompilationError } = cds.compiler
5
+ const { relativePaths, BuildError, resolveRequiredSapModels } = require('./util')
6
+ const { SEVERITIES, LOG_LEVELS } = require('./constants')
7
+ const BuildTaskFactory = require('./buildTaskFactory')
8
+ const BuildTaskProviderFactory = require('./buildTaskProviderFactory')
9
+ const BuildTaskHandlerInternal = require('./provider/buildTaskHandlerInternal')
10
+
11
+ const COMPILATION_ERROR = 'CompilationError'
12
+ const COMPILE_MESSAGE = 'CompileMessage'
13
+
14
+ class BuildTaskEngine {
15
+ constructor(options) {
16
+ this._providerFactory = new BuildTaskProviderFactory(options)
17
+ }
18
+ get providerFactory() {
19
+ return this._providerFactory
20
+ }
21
+ get logger() {
22
+ return this.providerFactory.logger
23
+ }
24
+ get buildOptions() {
25
+ return this.providerFactory.buildOptions
26
+ }
27
+
28
+ async processTasks(tasks) {
29
+ const startTime = Date.now()
30
+ const messages = []
31
+
32
+ this.logger.log(`building project [${cds.root}], clean [${this.buildOptions.clean}]`)
33
+ this.logger.log(`cds [${cds.version}], compiler [${cds.compiler.version()}], home [${cds.home}]\n`)
34
+
35
+ if (!tasks) {
36
+ const buildTaskFactory = new BuildTaskFactory(this.buildOptions)
37
+ tasks = await buildTaskFactory.getTasks()
38
+ }
39
+
40
+ // validate required @sap namespace models - log only
41
+ const unresolved = BuildTaskEngine._resolveRequiredSapServices(tasks)
42
+ if (unresolved.length > 0) {
43
+ throw new BuildError(`Required CDS models [${unresolved.join(', ')}] cannot be resolved. Make sure up-to-date versions of the missing modules are installed.`)
44
+ }
45
+
46
+ // create build task handlers
47
+ const handlers = []
48
+ tasks.forEach((task) => {
49
+ if (task) {
50
+ const handler = this._createHandler(task)
51
+ handlers.push(handler)
52
+ }
53
+ })
54
+
55
+ // use resolved tasks
56
+ this.providerFactory.context.tasks = handlers.map(handler => handler.task)
57
+
58
+ try {
59
+ await this._executePrepare(handlers)
60
+ await this._executeCleanBuildTasks(handlers)
61
+
62
+ // throwing Exception in case of compilation errors
63
+ const buildResult = await this._executeBuildTasks(handlers)
64
+
65
+ await this._writeGenerationLog(handlers)
66
+ this._logBuildOutput(handlers)
67
+ this._logMessages([...BuildTaskEngine._getHandlerMessages(handlers), ...messages])
68
+ this._logTimer(startTime, Date.now())
69
+
70
+ return buildResult
71
+ } catch (error) {
72
+ this._logBuildOutput(handlers)
73
+
74
+ // cds CLI layer logs in case of an exception if invoked from CLI
75
+ if (!this.buildOptions.cli) {
76
+ this._logMessages([...BuildTaskEngine._getErrorMessages([error]), ...messages])
77
+ }
78
+
79
+ throw error
80
+ }
81
+ }
82
+
83
+ /**
84
+ * BuildTaskHandler#prepare has been deprecated and was never part of the public API.
85
+ * Currently only used by internal FioriBuildTaskHandler.
86
+ * @deprecated
87
+ * @param {*} handlers
88
+ * @returns
89
+ */
90
+ async _executePrepare(handlers) {
91
+ const handlerGroups = new Map()
92
+
93
+ // group handlers by type
94
+ handlers.forEach(handler => {
95
+ handlerGroups.has(handler.task.for) ? handlerGroups.get(handler.task.for).push(handler) : handlerGroups.set(handler.task.for, [handler])
96
+ })
97
+
98
+ const promises = []
99
+ for (let handlerGroup of handlerGroups.values()) {
100
+ promises.push(this._doPrepare(handlerGroup))
101
+ }
102
+ return Promise.all(promises)
103
+ }
104
+
105
+ /**
106
+ * @deprecated
107
+ * @param {*} handlerGroup
108
+ */
109
+ async _doPrepare(handlerGroup) {
110
+ for (let handler of handlerGroup) {
111
+ // prepare has been deprecated
112
+ if (handler instanceof BuildTaskHandlerInternal) {
113
+ this.logger._debug && this.logger.debug(`preparing, handler [${handler.constructor.name}], src [${relativePaths(cds.root, handler.task.src)}]`)
114
+ const result = await handler.prepare()
115
+ if (result === false) {
116
+ break
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ async _executeCleanBuildTasks(handlers) {
123
+ if (this.buildOptions.clean) {
124
+ // clean entire build staging folder once
125
+ if (cds.env.build.target !== '.') {
126
+ const target = path.resolve(cds.root, cds.env.build.target)
127
+ this.logger._debug && this.logger.debug(`cleaning staging folder ${target}`)
128
+ await fs.promises.rm(target, { force: true, recursive: true })
129
+ }
130
+
131
+ const results = await Promise.allSettled(handlers.map((handler) => {
132
+ this.logger._debug && this.logger.debug(`cleaning, handler [${handler.constructor.name}], src [${relativePaths(cds.root, handler.task.src)}]`)
133
+ return handler.clean()
134
+ }))
135
+ // check for errors and throw exception
136
+ this._resolveHandlerResponse(results)
137
+ }
138
+ }
139
+
140
+ async _executeBuildTasks(handlers) {
141
+ // sort handlers based on priority in
142
+ handlers = handlers.sort((a, b) => {
143
+ return a.priority === b.priority ? 0 : a.priority > b.priority ? -1 : 1
144
+ })
145
+ // group handlers with same priority in order to execute in parallel
146
+ const buildPipeline = handlers.reduce((acc, handler) => {
147
+ if (acc.length === 0) {
148
+ acc.push([handler])
149
+ } else {
150
+ const currGroup = acc[acc.length - 1]
151
+ if (currGroup[0].priority === handler.priority) {
152
+ currGroup.push(handler)
153
+ } else {
154
+ acc.push([handler])
155
+ }
156
+ }
157
+ return acc
158
+ }, [])
159
+
160
+ const results = await this._executePipeline(buildPipeline)
161
+
162
+ // check for errors and throw exception - return results otherwise including any compiler and build status messages
163
+ return this._resolveHandlerResponse(results, BuildTaskEngine._getHandlerMessages(handlers))
164
+ }
165
+
166
+ async _executePipeline(pipeline) {
167
+ let allResults = []
168
+ for (const group of pipeline) {
169
+ const results = await Promise.allSettled(group.map((handler) => {
170
+ this.logger._debug && this.logger.debug(`building, handler [${handler.constructor.name}], src [${relativePaths(cds.root, handler.task.src)}]`)
171
+ return handler.build()
172
+ .then(handlerResult => {
173
+ return Promise.resolve({
174
+ task: handler.task,
175
+ result: handlerResult,
176
+ messages: this._sortMessagesUnique(handler.messages)
177
+ })
178
+ })
179
+ }))
180
+ allResults = allResults.concat(results)
181
+ }
182
+ return allResults
183
+ }
184
+
185
+ _resolveHandlerResponse(results, handlerMessages = []) {
186
+ const errors = []
187
+ const resolvedResults = results.reduce((acc, r) => {
188
+ if (r.status === 'fulfilled') {
189
+ acc.push(r.value)
190
+ }
191
+ if (r.status === 'rejected' && r.reason) {
192
+ errors.push(r.reason)
193
+ }
194
+ return acc
195
+ }, [])
196
+
197
+ if (errors.length > 0) {
198
+ const error = errors.find(e => e.constructor.name !== COMPILATION_ERROR)
199
+ if (error) {
200
+ // throw original error, including BuildErrors
201
+ // for BuildErrors we do not merge other build messages that might exist in order to keep the original error intact
202
+ throw error
203
+ }
204
+
205
+ // propagate existing CompilationErrors
206
+ // merge all existing compilation messages into a single CompilationError
207
+ // compiler warning and info messages are returned as handler messages
208
+ const compileErrors = errors.filter(e => e.constructor.name === COMPILATION_ERROR)
209
+ const compileMessages = handlerMessages.filter(message => message.constructor.name === COMPILE_MESSAGE)
210
+ if (compileErrors.length) {
211
+ throw new CompilationError(this._sortMessagesUnique(BuildTaskEngine._getErrorMessages(compileErrors), compileMessages))
212
+ }
213
+ }
214
+ return resolvedResults
215
+ }
216
+
217
+ _createHandler(task) {
218
+ try {
219
+ const handler = this.providerFactory.createHandler(task)
220
+ handler.init()
221
+
222
+ if (!(handler instanceof BuildTaskHandlerInternal) && handler.priority !== 1) {
223
+ // Custom build handlers are executed before internal handlers to ensure
224
+ // that generated content cannot be overwritten by mistake
225
+ throw new Error(`Illegal priority for ${handler.constructor.name} encountered for custom handler - in this version only priority value '1' is allowed`)
226
+ }
227
+ this._logTaskHandler(handler)
228
+
229
+ return handler
230
+ } catch (error) {
231
+ log(error, { log: this.logger.log })
232
+ throw error
233
+ }
234
+ }
235
+
236
+ _logBuildOutput(handlers) {
237
+ // log all generated files
238
+ const files = BuildTaskEngine._getBuildOutput(handlers)
239
+ if (files.length > 0) {
240
+ this.logger.log(`done > wrote output to:\n ${files.join("\n ")}\n`)
241
+ }
242
+ }
243
+
244
+ async _writeGenerationLog(handlers) {
245
+ const outputFile = cds.env.build.outputfile || process.env.GENERATION_LOG
246
+ if (outputFile) {
247
+ const files = BuildTaskEngine._getBuildOutput(handlers)
248
+ this.logger.log(`writing generation log to [${outputFile}]\n`)
249
+ try {
250
+ await fs.promises.mkdir(path.dirname(outputFile), { recursive: true })
251
+ await fs.promises.writeFile(outputFile, files.join('\n'))
252
+ } catch (error) {
253
+ this.logger.error(`failed to write generation log`)
254
+ this.logger.error(error.stack || error)
255
+ }
256
+ }
257
+ }
258
+
259
+ static _getBuildOutput(handlers) {
260
+ const files = handlers.reduce((acc, handler) => acc.concat(handler.files), []).sort()
261
+ return files.map(file => {
262
+ if (path.isAbsolute(cds.env.build.target)) {
263
+ return file
264
+ }
265
+ return path.relative(cds.root, file)
266
+ })
267
+ }
268
+
269
+ _logTimer(start, end) {
270
+ this.logger.log(`build completed in ${end - start} ms\n`)
271
+ }
272
+
273
+ _logTaskHandler(handler) {
274
+ this.logger._debug && this.logger.debug(`handler ${handler.constructor.name}`)
275
+ this.logger._debug && this.logger.debug(`details src [${relativePaths(cds.root, handler.task.src)}], dest [${relativePaths(cds.root, handler.task.dest)}], use [${handler.task.use}], options [${JSON.stringify(handler.task.options)}]`) //NOSONAR
276
+ }
277
+
278
+ _logMessages(messages) {
279
+ if (messages.length > 0) {
280
+ const options = {
281
+ log: this.logger.log,
282
+ "log-level": this._getLogLevel() // ensures that for tests the correct cds.env is used
283
+ }
284
+ deduplicateMessages(messages)
285
+ log(messages, options)
286
+ }
287
+ }
288
+
289
+ static _resolveRequiredSapServices(tasks) {
290
+ const taskModelPaths = tasks.reduce((acc, task) => {
291
+ const model = task.options?.model
292
+ if (model) {
293
+ if (Array.isArray(model)) {
294
+ model.forEach(m => acc.add(m))
295
+ } else {
296
+ acc.add(model)
297
+ }
298
+ }
299
+ return acc
300
+ }, new Set())
301
+
302
+ return resolveRequiredSapModels([...taskModelPaths])
303
+ }
304
+
305
+ /**
306
+ * Returns a sorted and flattened list of all messages extracted from the given errors.
307
+ * @param {Array<Error>} errors
308
+ */
309
+ static _getErrorMessages(errors) {
310
+ let messages = []
311
+ // flatten all compile messages in order to filter duplicates and sort later on
312
+ errors.forEach(error => {
313
+ if (Array.isArray(error.errors) && error.errors.length > 0) {
314
+ messages = messages.concat(this._getErrorMessages(error.errors))
315
+ } else {
316
+ messages.push(error)
317
+ }
318
+ })
319
+ return messages
320
+ }
321
+
322
+ /**
323
+ * Returns compiler messages and validation messages issued by handlers.
324
+ * @param {Array<BuildTaskHandler>} handlers
325
+ */
326
+ static _getHandlerMessages(handlers) {
327
+ return handlers.reduce((acc, handler) => acc.concat(handler.messages), [])
328
+ }
329
+
330
+ /**
331
+ * Sort and filter the given errors of type CompileMessage or BuildMessage according to their severity and location,
332
+ * but leave any other errors untouched as part of the result array.<br>
333
+ * The log level passed as 'buildOptions.log-level' or defined in 'cds.env' is used to filter the given messages.
334
+ * @param {...Error} messages
335
+ */
336
+ _sortMessagesUnique(...messages) {
337
+ const logLevelIdx = LOG_LEVELS.indexOf(this._getLogLevel())
338
+ // flatten
339
+ messages = messages.reduce((acc, m) => acc.concat(m), [])
340
+ // filter according to log-level
341
+ const filteredMessages = messages.filter(message => !message.severity || logLevelIdx >= SEVERITIES.indexOf(message.severity))
342
+ // remove duplicates
343
+ deduplicateMessages(filteredMessages)
344
+ // sort
345
+ return sortMessagesSeverityAware(filteredMessages)
346
+ }
347
+
348
+ /**
349
+ * Return user defined log level or default value 'warn'
350
+ */
351
+ _getLogLevel() {
352
+ return this.buildOptions["log-level"] || cds.env["log-level"]
353
+ }
354
+ }
355
+ module.exports = BuildTaskEngine
@@ -0,0 +1,284 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const cds = require('../cds'), { _log:log } = cds
4
+ const BuildTaskProviderFactory = require('./buildTaskProviderFactory')
5
+ const { hasJavaNature, getProperty, redactCredentials, flatten, getDefaultModelOptions } = require('./util')
6
+ const { FILE_EXT_CDS, BUILD_TASK_JAVA_CF, BUILD_TASK_JAVA } = require("./constants")
7
+
8
+ class BuildTaskFactory {
9
+ constructor(options) {
10
+ this._providerFactory = new BuildTaskProviderFactory(options)
11
+ }
12
+ get providerFactory() {
13
+ return this._providerFactory
14
+ }
15
+ get logger() {
16
+ return this.providerFactory.logger
17
+ }
18
+ get buildOptions() {
19
+ return this.providerFactory.buildOptions
20
+ }
21
+
22
+ // the following order for determining build tasks is used
23
+ // 1. create from command line input, e.g. cds build/all --for hana --src db --model srv --dest db
24
+ // 2. read using cds.env.build.tasks
25
+ // 3. create from cds.env.folders config data
26
+ async getTasks() {
27
+ try {
28
+ const tasks = await this._createTasks()
29
+
30
+ // log build tasks
31
+ this.logger.log(`the following build tasks will be executed`)
32
+ let tasksOutput = " {\n \"build\": {\n \"target\": \"" + cds.env.build.target + "\",\n \"tasks\": ["
33
+ for (let i = 0; i < tasks.length; i++) {
34
+ tasksOutput += "\n " + BuildTaskFactory._stringifyTask(tasks[i]) + (i + 1 < tasks.length ? "," : "")
35
+ }
36
+ tasksOutput += "\n ]\n }\n }\n"
37
+ this.logger.log(tasksOutput)
38
+
39
+ // for testing purposes
40
+ this.logger.debug("cds.env used for build:")
41
+ this.logger._debug && this.logger.debug(redactCredentials(cds.env))
42
+
43
+ // always resolve tasks for input validation
44
+ const resolvedTasks = this.providerFactory.resolveTasks(tasks)
45
+ if (this.buildOptions.resolve) {
46
+ return resolvedTasks
47
+ }
48
+ return tasks
49
+ } catch (e) {
50
+ this.logger.log('')
51
+ // cds CLI layer is doing the logging if invoked using CLI
52
+ if (!this.buildOptions.cli) {
53
+ log(e, { withStack: true, log: (message) => this.logger.log(message) })
54
+ }
55
+ throw e
56
+ }
57
+ }
58
+
59
+ async _createTasks() {
60
+ this.logger.debug(`determining build tasks for project [${cds.root}].`)
61
+
62
+ // clearing model cache (details https://github.tools.sap/cap/cds/pull/181) is no longer required
63
+ // because of changes https://github.tools.sap/cap/cds/pull/1121
64
+ // cds.resolve.cache = {}
65
+
66
+ let tasks = BuildTaskFactory._getExistingTasks()
67
+ if (tasks.length === 0) {
68
+ tasks = await this.providerFactory.lookupTasks()
69
+ } else {
70
+ // 1. apply default values including task.for and task.use and ensure that for all tasks a provider exists - throwing error otherwise
71
+ await this.providerFactory.applyTaskDefaults(tasks)
72
+ // ensure that dependencies get wired up before filtering
73
+ await this.providerFactory.lookupTasks(tasks)
74
+ }
75
+
76
+ // 3. filters the list of build tasks and adapts according to given CLI options
77
+ // Note: A new task might get created, e.g. 'cds build --for hana' will enforce a hana build even if sqlite has been configured
78
+ let existingTasks = tasks
79
+ tasks = this._applyCliOptionsFilter(tasks)
80
+ if (tasks.length === 0) {
81
+ return tasks
82
+ }
83
+ // a new array is returned to indicate new build tasks have been created
84
+ if (tasks !== existingTasks) {
85
+ // a different task shall be executed
86
+ await this.providerFactory.applyTaskDefaults(tasks)
87
+ }
88
+
89
+ // 2. add dependencies
90
+ existingTasks = [...tasks]
91
+ await this.providerFactory.lookupTasks(tasks)
92
+ if (tasks.length > existingTasks.length) {
93
+ const newTasks = tasks.filter(task => !existingTasks.includes(task))
94
+ // a dependant task was added
95
+ this._applyCliOptions(newTasks)
96
+ }
97
+
98
+ // obligatory task defaults shared by all tasks
99
+ BuildTaskFactory._applyCommonTaskDefaults(tasks)
100
+
101
+ // ensure correct values for optional build task properties, error for missing mandatory properties
102
+ BuildTaskFactory._validateBuildTasks(tasks)
103
+
104
+ this._setDefaultBuildTargetFolder(tasks)
105
+ return tasks
106
+ }
107
+
108
+ static _getExistingTasks() {
109
+ return Array.isArray(getProperty(cds.env, 'build.tasks')) ? JSON.parse(JSON.stringify(cds.env.build.tasks)) : []
110
+ }
111
+
112
+ static _applyCommonTaskDefaults(tasks) {
113
+ const modelPaths = getDefaultModelOptions()
114
+
115
+ tasks.forEach(task => {
116
+ this._setTaskModelOptions(task, modelPaths)
117
+ if (!task.src) {
118
+ throw new Error(`Invalid build task definition - value of property 'src' is missing in [${task.for || task.use}].`)
119
+ }
120
+ })
121
+ }
122
+
123
+ static _validateBuildTasks(tasks) {
124
+ tasks.forEach(task => {
125
+ if (!task.src) {
126
+ throw new Error(`Invalid build task definition - value of property 'src' is missing in [${task.for || task.use}].`)
127
+ }
128
+ })
129
+ }
130
+
131
+ _setDefaultBuildTargetFolder(tasks) {
132
+ const task = tasks.find(task => task.for === BUILD_TASK_JAVA_CF || task.for === BUILD_TASK_JAVA)
133
+ const srv = task ? task.src : BuildTaskFactory._getModuleFolder(flatten([cds.env.folders.srv])) || "srv"
134
+
135
+ // Java projects use "." as the default build target folder
136
+ if (hasJavaNature([path.join(cds.root, srv), cds.root]) && BuildTaskFactory._adaptBuildTargetSettingForJava()) {
137
+ this.logger.debug("using inplace build for java project instead of default staging build")
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Use inplace build for java projects if build.target has not been configured.
143
+ * @returns {boolean} true if changed, false otherwise
144
+ */
145
+ static _adaptBuildTargetSettingForJava() {
146
+ if (cds.env.build.target !== ".") {
147
+ // filter user settings of cds.env
148
+ const userEnv = cds.env.for("cds", cds.root, false)
149
+
150
+ // use helper as env.build might be undefined
151
+ if (!userEnv.build?.target) {
152
+ cds.env.build.target = "."
153
+ return true
154
+ }
155
+ }
156
+ return false
157
+ }
158
+
159
+ _applyCliOptionsFilter(tasks) {
160
+ const buildOptions = this.buildOptions
161
+ // filter tasks using either option for, use, src
162
+ let resultTasks = tasks.filter(task => {
163
+ return (!buildOptions.use || buildOptions.use === task.use)
164
+ && (!buildOptions.for || buildOptions.for === task.for)
165
+ && (!buildOptions.src || buildOptions.src === task.src)
166
+ })
167
+ if (resultTasks.length === 0) {
168
+ if (buildOptions.for || buildOptions.use) {
169
+ const task = {}
170
+ if ((buildOptions.for)) {
171
+ task.for = buildOptions.for
172
+ }
173
+ if ((buildOptions.use)) {
174
+ task.use = buildOptions.use
175
+ }
176
+ if (buildOptions.src) {
177
+ task.src = buildOptions.src
178
+ }
179
+ resultTasks.push(task)
180
+ }
181
+ } else if (resultTasks.length < tasks.length) {
182
+ // return the same array as long as it contains a subset of the given tasks
183
+ tasks.length = 0
184
+ resultTasks.forEach(task => tasks.push(task))
185
+ resultTasks = tasks
186
+ }
187
+ this._applyCliOptions(resultTasks)
188
+ return resultTasks
189
+ }
190
+
191
+ _applyCliOptions(tasks) {
192
+ const buildOptions = this.buildOptions
193
+ // apply remaining cli options to filtered tasks
194
+ tasks.forEach(task => {
195
+ if (buildOptions.dest) {
196
+ task.dest = buildOptions.dest
197
+ }
198
+ if (buildOptions.opts) {
199
+ const opts = BuildTaskFactory._scanTaskOptionParams(buildOptions.opts)
200
+ task.options = task.options ? Object.assign(task.options, opts) : opts
201
+ }
202
+ })
203
+ }
204
+
205
+ static _setTaskModelOptions(task, defaultModelPaths) {
206
+ task.options = task.options || {}
207
+ if (!task.options.model || Array.isArray(task.options.model) && task.options.model.length === 0) {
208
+ defaultModelPaths = new Set(defaultModelPaths)
209
+ if (task.src) {
210
+ defaultModelPaths.add(task.src)
211
+ }
212
+ task.options.model = [...defaultModelPaths]
213
+ } else if (!Array.isArray(task.options.model)) {
214
+ task.options.model = [task.options.model]
215
+ }
216
+ }
217
+
218
+ static _stringifyTask(task) {
219
+ // ensures identical order of properties
220
+ const order = ["for", "use", "src", "dest", "options"]
221
+ const keys = Object.keys(task).sort((a, b) => order.indexOf(a) - order.indexOf(b))
222
+
223
+ return keys.reduce((acc, key, idx) => {
224
+ // render either "for" OR "use" value
225
+ return acc + (!key.match("^_") ? (key !== "use" || keys[idx - 1] !== "for" ? (acc !== "{" ? ", " : "") + JSON.stringify(key) + ":" + JSON.stringify(task[key]) : "") : "")
226
+ }, "{") + "}"
227
+ }
228
+
229
+ static _scanTaskOptionParams(optsParams) {
230
+ // need to create new regex every call since a constant would keep the match state
231
+ const quoteRegex = /([\w-]+)=([\w/.]+|\[([\w/,.]+)\])/g
232
+
233
+ // captures a=1 => a:1
234
+ // a=[x,y,z] => a:[x,y,z]
235
+ // a=1,b=[x,y,z] => a:1 b=[x,y,z]
236
+ let match = quoteRegex.exec(optsParams)
237
+ const taskOptions = {}
238
+
239
+ while (match != null) {
240
+ const key = match[1]
241
+ const value = match[3] || match[2]
242
+ const valueArray = value.split(",")
243
+ taskOptions[key] = valueArray.length > 1 ? valueArray.map((entry) => entry.trim()) : value
244
+ match = quoteRegex.exec(optsParams)
245
+ }
246
+ return taskOptions
247
+ }
248
+
249
+ /**
250
+ * For valid paths remove trailing '/'. Otherwise return as is - important!!
251
+ * @param {*} dir
252
+ */
253
+ static _normalizePath(dir) {
254
+ return typeof dir === "string" ? dir.replace(/\/$/, '') : dir
255
+ }
256
+
257
+ /**
258
+ * Determines the module folder from the past list that may represent files or folders w or w/o .cds file extension.
259
+ * @param {Array} filesOrFolders
260
+ */
261
+ static _getModuleFolder(filesOrFolders) {
262
+ const resources = [...filesOrFolders]
263
+ filesOrFolders.forEach(fileOrFolder => {
264
+ if (path.extname(fileOrFolder) !== FILE_EXT_CDS) {
265
+ resources.push(fileOrFolder + FILE_EXT_CDS)
266
+ }
267
+ })
268
+ return resources.reduce((acc, resource) => {
269
+ if (!acc) {
270
+ let resourcePath = path.resolve(cds.root, resource)
271
+ if (fs.existsSync(resourcePath)) {
272
+ if (fs.lstatSync(resourcePath).isDirectory()) {
273
+ acc = resource
274
+ } else {
275
+ // represents file
276
+ acc = path.dirname(resource)
277
+ }
278
+ }
279
+ }
280
+ return acc
281
+ }, null)
282
+ }
283
+ }
284
+ module.exports = BuildTaskFactory