@sap/cds-dk 6.8.3 → 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 +337 -275
  211. package/package.json +6 -6
  212. package/CHANGELOG.md +0 -1460
  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,522 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const cds = require('../../../cds')
4
+ const to_hdbmigration = require('./2migration')
5
+ const BuildTaskHandlerInternal = require('../buildTaskHandlerInternal')
6
+ const { BuildError, relativePaths } = require('../../util')
7
+
8
+ const { OUTPUT_MODE_NONE, OUTPUT_MODE, CONTENT_PACKAGE_JSON, CONTENT_HDBTABLEDATA, CSV_FILE_DETECTION,
9
+ CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, CONTENT_NODE_MODULES, OUTPUT_MODE_RESULT } = require('../../constants')
10
+ const { ERROR, WARNING } = BuildTaskHandlerInternal
11
+
12
+ const DEFAULT_COMPILE_DEST_FOLDER = path.normalize("src/gen")
13
+
14
+ const FILE_EXT_CSV = ".csv"
15
+ const FILE_EXT_HDBTABLEDATA = ".hdbtabledata"
16
+ const FILE_EXT_HDBTABLE = ".hdbtable"
17
+ const FILE_EXT_HDBMIGRATIONTABLE = ".hdbmigrationtable"
18
+
19
+ const FILE_NAME_HDICONFIG = ".hdiconfig"
20
+ const FILE_NAME_HDINAMESPACE = ".hdinamespace"
21
+ const FILE_NAME_PACKAGE_JSON = "package.json"
22
+ const PATH_LAST_DEV_CSN = "last-dev/csn.json"
23
+ const FILE_NAME_UNDEPLOY_JSON = "undeploy.json"
24
+
25
+ const DEPLOY_FORMAT = "deploy-format"
26
+
27
+ class HanaModuleBuilder extends BuildTaskHandlerInternal {
28
+ init() {
29
+ if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
30
+ this._result = {
31
+ dest: this.task.dest,
32
+ hana: []
33
+ }
34
+ }
35
+ this.task.options.compileDest = path.resolve(this.task.dest, this.task.options.compileDest || DEFAULT_COMPILE_DEST_FOLDER)
36
+ }
37
+
38
+ async build() {
39
+ const { src, dest } = this.task
40
+ const model = await this.model()
41
+ if (!model) {
42
+ return
43
+ }
44
+ // the order is important
45
+ // 1. compile
46
+ const hdiPlugins = await this._compileToHana(model)
47
+
48
+ if (!this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_NONE)) {
49
+ // 2. copy resources
50
+ if (this.hasBuildOption(CSV_FILE_DETECTION, false)) {
51
+ await this._copyResources(src, dest)
52
+ } else {
53
+ await this._copyResourcesExt(src, dest, model)
54
+ }
55
+
56
+ // 3. create additional stuff in dest
57
+ await this._writeHdiConfig(hdiPlugins)
58
+ await this._writeHdiNamespace()
59
+
60
+ // TODO disabled as this contradicts the MTX domain concept which allows partial app deployments
61
+ //await this._writeUndeployJson()
62
+
63
+ if (!this.hasBuildOption(CONTENT_HDBTABLEDATA, false)) {
64
+ await this._compileToHdbtabledata(model)
65
+ }
66
+ if (!this.hasBuildOption(CONTENT_PACKAGE_JSON, false)) {
67
+ await this._writePackageJson()
68
+ }
69
+ }
70
+ return this._result
71
+ }
72
+
73
+ /**
74
+ * Deletes any content that has been created in folder '#this.task.dest/src/gen' by inplace mode.
75
+ * <br>
76
+ * Note: Content created in staging build will be deleted by the #BuildTaskEngine itself.
77
+ */
78
+ async clean() {
79
+ if (this.isStagingBuild()) {
80
+ return super.clean()
81
+ }
82
+ return fs.promises.rm(this.task.options.compileDest, { force: true, recursive: true })
83
+ }
84
+
85
+ /**
86
+ * Copies all files located at <src> (except HANA artifacts not contained in <db>/src/**) to the folder <dest>.
87
+ * '*.csv' files are read based on the corresponding CDS model file location and copied as flat list into folder '<dest>/src/gen>'.
88
+ *
89
+ * @param {string} src
90
+ * @param {string} dest
91
+ * @param {Object} model
92
+ */
93
+ async _copyResourcesExt(src, dest, model) {
94
+ const resources = Object.keys(await cds.deploy.resources(model)).reverse() // reverse to get reuse resources first and app resources last
95
+ const csvPath = path.join(src, "data")
96
+ // determine subfolder name used by the application for backward compatibility
97
+ const csvFolder = resources.some(res => res && res.startsWith(csvPath)) ? "data" : "csv"
98
+
99
+ // 1. copy csv files into 'src/gen/data' or 'src/gen/csv' subfolder
100
+ const dbSrc = path.join(src, 'src')
101
+
102
+ // await each copy as order is important - reuse resources first and app resources last
103
+ for (const res of resources) {
104
+ // do not duplicate resources that are already contained in db/src/**
105
+ if (res && /\.csv$/.test(res) && !res.startsWith(dbSrc)) {
106
+ await this.copy(res).to(path.join(this.task.options.compileDest, csvFolder, path.basename(res)))
107
+ }
108
+ }
109
+
110
+ // 2. staging build: copy files except *.cds, .env, default-env.json, ./node_modules/**
111
+ if (this.isStagingBuild()) {
112
+ let blockList = "\\.cds$|\\.csv$|\\.hdbtabledata$"
113
+ blockList += !this.hasBuildOption(CONTENT_ENV, true) ? "|\\.env($|\\..*$)" : ""
114
+ blockList += !this.hasBuildOption(CONTENT_DEFAULT_ENV_JSON, true) ? "|default-env\\.json$" : ""
115
+ blockList = new RegExp(blockList)
116
+
117
+ await this.copyNativeContent(src, dest, (entry) => {
118
+ if (entry.startsWith(dbSrc)) {
119
+ // entire native content
120
+ return true
121
+ }
122
+ if (fs.statSync(entry).isDirectory()) {
123
+ return !/(\/|\\)node_modules(\/|\\)?$/.test(entry) ||
124
+ (/(\/|\\)node_modules(\/|\\)?$/.test(entry) && this.hasBuildOption(CONTENT_NODE_MODULES, true))
125
+ }
126
+ return !blockList.test(entry)
127
+ })
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Copies the entire content of the db module located in the given <src> folder to the folder <dest>.
133
+ * '*.csv' and '*.hdbtabledata' files located in a subfolder 'data' or 'csv' will be copied to '<dest>/src/gen/data>'||'<dest>/src/gen/csv>'
134
+ *
135
+ * @param {string} src
136
+ * @param {string} dest
137
+ */
138
+ async _copyResources(src, dest) {
139
+ const dbCsvDir = path.join(src, "csv")
140
+ const dbDataDir = path.join(src, "data")
141
+ const csvDirs = [dbCsvDir, dbDataDir]
142
+ const regexData = RegExp('\\.csv$|\\.hdbtabledata$')
143
+
144
+ if (this.isStagingBuild()) {
145
+ const regex = RegExp('\\.cds$|\\.csv$|\\.hdbtabledata$')
146
+
147
+ await this.copyNativeContent(src, dest, (entry) => {
148
+ if (fs.statSync(entry).isDirectory()) {
149
+ return !/(\/|\\)node_modules(\/|\\)?$/.test(entry)
150
+ }
151
+ return (!regex.test(entry) && entry !== cds.env.build.outputfile) ||
152
+ (regexData.test(entry) && !entry.startsWith(dbCsvDir) && !entry.startsWith(dbDataDir))
153
+ })
154
+ }
155
+
156
+ // handle *.csv and *.hdbtabledata located in '<dbSrc>/data' and '<dbSrc>/csv' folder
157
+ const allFiles = csvDirs.reduce((acc, csvDir) => {
158
+ return acc.concat(BuildTaskHandlerInternal._find(csvDir, (entry) => {
159
+ if (fs.statSync(entry).isDirectory()) {
160
+ return false
161
+ }
162
+ return regexData.test(entry)
163
+ }))
164
+ }, [])
165
+
166
+ return Promise.all(allFiles.map((file) => {
167
+ return this.copy(file).to(path.join(this.task.options.compileDest, path.relative(src, file)))
168
+ }))
169
+ }
170
+
171
+ /**
172
+ * Generates *.hdbtabledata files in folder '#this.task.dest/src/gen' from *.csv files located in '#this.task.dest/src/**' folder.
173
+ * The generated *.hdbtabledata files will link to their *.csv counterparts using relative links. The *.csv files have either
174
+ * already been defined in the 'src' folder or they have been copied to '#this.task.dest/src/gen/**' folder if they have been
175
+ * created outside 'src' folder. If custom *.hdbtabledata files are found nothing is generated for this particular folder.
176
+ * <br>
177
+ * Note: *.csv and *.hdbtabledata need to be copied to '#this.task.dest/src/gen**' if required before this method is called.
178
+ * In inplace mode dest folder is refering to src folder.
179
+ *
180
+ * @param {object} model compiled csn
181
+ */
182
+ async _compileToHdbtabledata(model) {
183
+ const tableDataDirs = new Set()
184
+ const destSrcDir = path.join(this.task.dest, "src")
185
+
186
+ const allCsvFiles = BuildTaskHandlerInternal._find(destSrcDir, (entry) => {
187
+ if (fs.statSync(entry).isDirectory()) {
188
+ return true
189
+ }
190
+ if (/\.hdbtabledata$/.test(entry)) {
191
+ tableDataDirs.add(path.dirname(entry))
192
+ }
193
+ return /\.csv$/.test(entry)
194
+ })
195
+ if (allCsvFiles.length > 0) {
196
+ const csvDirs = allCsvFiles.map(path.dirname).reduce((dirs, dir) => {
197
+ if (!tableDataDirs.has(dir) && !dirs.includes(dir)) { // exclude any dir where a tabledata is present
198
+ dirs.push(dir)
199
+ }
200
+ return dirs
201
+ }, [])
202
+
203
+ // ODM csv data comes with license comments, so strip these
204
+ if (!this.hasBuildOption("stripCsvComments", false)) {
205
+ await this._stripCsvComments(allCsvFiles)
206
+ }
207
+
208
+ const promises = []
209
+ const relDest = path.relative(this.task.dest, this.task.options.compileDest)
210
+ const options = { ...this.options(), logger: this.logger, dirs: csvDirs, baseDir: this.task.options.compileDest }
211
+
212
+ const toHdbtabledata = cds.compile.to.hdbtabledata ?? require(path.join(cds.home, 'bin/build/provider/hana/2tabledata')) // cds@6 compatibility
213
+ const tableDataResult = await toHdbtabledata(model, options)
214
+
215
+ for (let [tableData, { file, csvFolder }] of tableDataResult) {
216
+ // create .hdbtabledata side-by-side if .csv is contained in 'src/gen/**' subfolder
217
+ // otherwise create in 'src/gen'
218
+ let tableDataPath = csvFolder.startsWith(this.task.options.compileDest) ? csvFolder : this.task.options.compileDest
219
+ tableDataPath = path.join(tableDataPath, file)
220
+ if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
221
+ this._result.hana.push(path.join(relDest, file))
222
+ }
223
+ promises.push(this.write(tableData).to(tableDataPath))
224
+ }
225
+ await Promise.all(promises)
226
+ }
227
+ }
228
+
229
+ async _stripCsvComments(csvFiles) {
230
+ // Note: modification of csv files is only allowed for files located in the compile destination folder,
231
+ // meaning having their origin location at db/data/* or db/csv/*
232
+ const stripComments = cds.utils.csv?.stripComments ?? require(path.join(cds.home, 'bin/build/csv-reader')).stripComments // cds@6 compatibility
233
+
234
+ for (const file of csvFiles) {
235
+ if (this.isStagingBuild() || file.startsWith(this.task.options.compileDest)) {
236
+ await stripComments(file)
237
+ }
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Creates the hana artifacts from the given csn model and writes the files to the folder '<dest>/src/gen'.
243
+ *
244
+ * @param {object} model The compiled csn model
245
+ */
246
+ async _compileToHana(model) {
247
+ // compile to old format (.hdbcds) or new format (.hdbtable / .hdbview)
248
+ const format = this.getBuildOption(DEPLOY_FORMAT) || cds.env.requires.db?.[DEPLOY_FORMAT] || cds.env.hana?.[DEPLOY_FORMAT]
249
+ if (!cds.compile.to[format]) {
250
+ return Promise.reject(new Error(`Invalid deploy-format defined: ${format}`))
251
+ }
252
+
253
+ if (this.hasCdsEnvOption('features.journal', false) || format === 'hdbcds') {
254
+ return await this._compileToHdb(model, format)
255
+ } else {
256
+ return await this._compileToHdbmigration(model, format)
257
+ }
258
+ }
259
+
260
+ async _compileToHdb(model, format) {
261
+ const hdiPlugins = new Set()
262
+ const relDest = path.relative(this.task.dest, this.task.options.compileDest)
263
+ // enforces sqlNames option for compiler in tests
264
+ const options = { ...this.options(), sql_mapping: cds.env.sql.names }
265
+ const result = cds.compile.to[format](model, options)
266
+ const promises = []
267
+
268
+ for (const [content, key] of result) {
269
+ hdiPlugins.add(key.suffix || path.extname(key.file))
270
+ const file = key.file ? key.file : key.name + key.suffix
271
+ if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
272
+ this._result.hana.push(path.join(relDest, file))
273
+ }
274
+ promises.push(this.write(content).to(path.join(this.task.options.compileDest, file)))
275
+ }
276
+ await Promise.all(promises)
277
+ return hdiPlugins
278
+ }
279
+
280
+ async _compileToHdbmigration(model, format) {
281
+ const hdiPlugins = new Set()
282
+ const relDestDir = path.relative(this.task.dest, this.task.options.compileDest)
283
+ const relDbDestDir = path.relative(cds.root, this.task.options.compileDest)
284
+ const dbSrcDir = path.join(this.task.src, "src")
285
+ const relDbSrcDir = path.relative(cds.root, dbSrcDir)
286
+ const lastDevCsnFolder = PATH_LAST_DEV_CSN
287
+ const lastDevCsnDir = path.join(this.task.src, lastDevCsnFolder)
288
+ let lastDev = null
289
+ const promises = []
290
+ const migrationTableFiles = []
291
+
292
+ if (fs.existsSync(lastDevCsnDir)) {
293
+ lastDev = JSON.parse((await fs.promises.readFile(lastDevCsnDir, 'utf-8')).toString())
294
+ }
295
+ // enforces sqlNames option for compiler in tests, pass options from cds env, ensures that the correct format is taken
296
+ const options = { ...this.options(), logger: this.logger, sql_mapping: cds.env.sql.names, "deploy-format": format }
297
+
298
+ const compilationResult = await to_hdbmigration(model, lastDev, dbSrcDir, options)
299
+ const definitions = compilationResult.definitions
300
+ const afterImage = compilationResult.afterImage
301
+
302
+ for (const { name, suffix, content, changed } of definitions) {
303
+ hdiPlugins.add(suffix)
304
+ const file = name + suffix
305
+ if (suffix === FILE_EXT_HDBMIGRATIONTABLE) {
306
+ migrationTableFiles.push(path.join(relDbSrcDir, file))
307
+ if (changed) {
308
+ if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
309
+ this._result.hana.push(path.join("src", file))
310
+ }
311
+ promises.push(this.write(content).to(path.join(dbSrcDir, file)))
312
+ } else {
313
+ this.logger._debug && this.logger.debug(`no change, keep existing ${file}`)
314
+ }
315
+ } else {
316
+ if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
317
+ this._result.hana.push(path.join(relDestDir, file))
318
+ }
319
+ promises.push(this.write(content).to(path.join(this.task.options.compileDest, file)))
320
+ if (suffix === FILE_EXT_HDBTABLE) {
321
+ // issue an error in case a .hdbmigrationtable file already exists
322
+ if (fs.existsSync(path.join(dbSrcDir, name + FILE_EXT_HDBMIGRATIONTABLE))) {
323
+ throw new BuildError(`Multiple files exist defining the same HANA artifact - [${path.join(relDbSrcDir, name + FILE_EXT_HDBMIGRATIONTABLE)}, ${path.join(relDbDestDir, file)}].\nEither annotate the model entity using @cds.persistence.journal or undeploy the file [${path.join('src', name + FILE_EXT_HDBMIGRATIONTABLE)}] using an undeploy.json file.`)
324
+ }
325
+ }
326
+ }
327
+ }
328
+ await Promise.all(promises)
329
+ await this._validateMigrationTableFiles()
330
+
331
+ // update last development version
332
+ if (afterImage) {
333
+ if (migrationTableFiles.length > 0) {
334
+ if (!HanaModuleBuilder._toEqualIgnoreMeta(lastDev, afterImage)) {
335
+ await this.write(afterImage).to(lastDevCsnDir)
336
+ }
337
+
338
+ // add src/.hdiconfig if not existing
339
+ if (!fs.existsSync(path.join(dbSrcDir, FILE_NAME_HDICONFIG))) {
340
+ const template = await HanaModuleBuilder._readTemplateAsJson('.hdiconfig-cloud')
341
+ await this.write(template).to(path.join(dbSrcDir, FILE_NAME_HDICONFIG))
342
+ }
343
+ }
344
+ } else {
345
+ throw new BuildError(`Inconsistent CDS compilation results - file ${lastDevCsnFolder} missing`)
346
+ }
347
+ return hdiPlugins
348
+ }
349
+
350
+ async _writePackageJson() {
351
+ const packageJson = path.join(this.task.src, "package.json")
352
+ const exists = fs.existsSync(packageJson)
353
+
354
+ if (exists) {
355
+ this.logger._debug && this.logger.debug(`skip create [${relativePaths(cds.root, packageJson)}], already existing`)
356
+ }
357
+ if (this.isStagingBuild() && !exists) {
358
+ const content = await HanaModuleBuilder._readTemplateAsJson(FILE_NAME_PACKAGE_JSON)
359
+ await this.write(content).to(path.join(this.task.dest, FILE_NAME_PACKAGE_JSON))
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Create .hdiconfig file in <dest>src/gen folder of db module.
365
+ */
366
+ async _writeHdiConfig(compileHdiPlugins) {
367
+ const DEFAULT_HDI_PLUGINS = [FILE_EXT_CSV, FILE_EXT_HDBTABLEDATA, FILE_EXT_HDBTABLE, ".hdbview", ".hdbindex", ".hdbconstraint"]
368
+ const undeployHdiPlugins = await this._readTypesFromUndeployJson() // see CAP issue #6222
369
+ const hdiPlugins = new Set([...DEFAULT_HDI_PLUGINS, ...undeployHdiPlugins, ...compileHdiPlugins])
370
+ let hdiConfigPath
371
+
372
+ // do not create the .hdiconfig file in 'gen/db' root folder for now
373
+ // if (this.isStagingBuild()
374
+ // && !fs.existsSync(path.join(this.task.dest, FILE_NAME_HDICONFIG))
375
+ // && !fs.existsSync(path.join(this.task.dest, 'cfg', FILE_NAME_HDICONFIG))) {
376
+ // // in 'gen/db' if none exists
377
+ // // ensures correct deployment of all artifacts including HANA native artifacts
378
+ // // without the need of creating a static .hdiconfig file in the 'db' folder
379
+
380
+ // // add all known plugins
381
+ // const defaultHdiConfig = await HanaModuleBuilder._readTemplateAsJson('.hdiconfig-cloud')
382
+ // for (const plugin in defaultHdiConfig['file_suffixes']) {
383
+ // hdiPlugins.add('.' + plugin)
384
+ // }
385
+ // hdiConfigPath = path.join(this.task.dest, FILE_NAME_HDICONFIG)
386
+ // } else {
387
+ // in 'gen/db/src/gen'
388
+ // ensures correct deployment of generated and undeploy.json artifacts
389
+ // a static .hdiconfig file in 'db' folder is required for any HANA native artifacts
390
+ hdiConfigPath = path.join(this.task.options.compileDest, FILE_NAME_HDICONFIG)
391
+ // }
392
+
393
+ const template = await HanaModuleBuilder._readTemplateAsJson('.hdiconfig-all')
394
+ let content = {
395
+ 'file_suffixes': {}
396
+ }
397
+ // only use the required subset as SAP HANA cloud does not support all hdiPlugins
398
+ for (const key in template['file_suffixes']) {
399
+ if (hdiPlugins.has('.' + key)) {
400
+ content['file_suffixes'][key] = template['file_suffixes'][key]
401
+ }
402
+ }
403
+ if (Object.keys(content['file_suffixes']).length !== hdiPlugins.size) {
404
+ this.pushMessage(`'HANA database plugin not found for file suffix [${Array.from(hdiPlugins).join(',')}]`)
405
+ }
406
+ await this.write(content).to(hdiConfigPath)
407
+ }
408
+
409
+ /**
410
+ * Create .hdinamespace file in <dest>src/gen folder of db module.
411
+ */
412
+ async _writeHdiNamespace() {
413
+ // see issue #64 - add .hdinamespace file to prevent HDI from adding gen/ folder to the namespace.
414
+ const hdiNamespace = path.join(this.task.options.compileDest, FILE_NAME_HDINAMESPACE)
415
+ const content = await HanaModuleBuilder._readTemplateAsJson(FILE_NAME_HDINAMESPACE)
416
+ return await this.write(content).to(hdiNamespace)
417
+ }
418
+
419
+ /**
420
+ * Create undeploy.json file in <dest> folder of db module.
421
+ */
422
+ async _writeUndeployJson() {
423
+ if (this.isStagingBuild()) {
424
+ // see issue #64 - add .hdinamespace file to prevent HDI from adding gen/ folder to the namespace.
425
+ const undeployJsonDest = path.join(this.task.dest, FILE_NAME_UNDEPLOY_JSON)
426
+ const undeployJsonSrc = path.join(this.task.src, FILE_NAME_UNDEPLOY_JSON)
427
+ const templateEntries = await HanaModuleBuilder._readTemplateAsJson(FILE_NAME_UNDEPLOY_JSON)
428
+ let newEntries = []
429
+ if (fs.existsSync(undeployJsonSrc)) {
430
+ newEntries = await JSON.parse((await fs.promises.readFile(undeployJsonSrc, 'utf-8')).toString())
431
+ newEntries = Array.isArray(newEntries) ? newEntries : []
432
+ templateEntries.forEach(entry => {
433
+ if (!newEntries.includes(entry)) {
434
+ newEntries.push(entry)
435
+ }
436
+ })
437
+ } else {
438
+ newEntries = templateEntries
439
+ }
440
+ // formatted output
441
+ let content = '[\n'
442
+ for (let i = 0; i < newEntries.length; i++) {
443
+ content += ` "${newEntries[i]}"${i + 1 < newEntries.length ? ',' : ''}\n`
444
+ }
445
+ content += ']'
446
+ await this.write(content).to(undeployJsonDest)
447
+ }
448
+ }
449
+
450
+ async _readTypesFromUndeployJson() {
451
+ const hdiPlugins = new Set()
452
+ const file = path.join(this.task.src, "undeploy.json")
453
+
454
+ if (fs.existsSync(file)) {
455
+ const undeployList = JSON.parse((await fs.promises.readFile(file)).toString(), 'utf-8')
456
+ if (Array.isArray(undeployList)) {
457
+ const hdiconfig = await HanaModuleBuilder._readTemplateAsJson('.hdiconfig-all')
458
+ const keys = new Set(Object.keys(hdiconfig['file_suffixes']).map(key => '.' + key))
459
+ undeployList.forEach(entry => {
460
+ const extName = path.extname(entry)
461
+ if (extName && !hdiPlugins.has(extName)) {
462
+ if (keys.has(extName)) {
463
+ hdiPlugins.add(extName)
464
+ } else {
465
+ this.pushMessage(`Ignoring invalid entry '${entry}' in undeploy.json file`, WARNING)
466
+ }
467
+ }
468
+ })
469
+ }
470
+ }
471
+ return hdiPlugins
472
+ }
473
+
474
+ async _validateMigrationTableFiles() {
475
+ const dbSrcDir = path.join(this.task.src, "src")
476
+ const migrationTableFiles = BuildTaskHandlerInternal._find(dbSrcDir, (res) => {
477
+ return fs.statSync(res).isFile() && path.extname(res) === FILE_EXT_HDBMIGRATIONTABLE
478
+ })
479
+ if (migrationTableFiles.length > 0) {
480
+ const parser = require('./migrationtable')
481
+
482
+ await Promise.all(migrationTableFiles.map(async file => {
483
+ try {
484
+ const tableModel = await parser.read(file)
485
+ if (/^>>>>>/m.test(tableModel.migrations.toString())) {
486
+ // as this is not a build error, we do not abort cds build, instead only log as error
487
+ this.pushMessage(`Current model changes require manual resolution. See migration file ${path.relative(cds.root, file)} for further details.`, ERROR)
488
+ }
489
+ } catch (e) {
490
+ throw new Error(`${path.relative(cds.root, file)}: ${e.toString()}`, ERROR)
491
+ }
492
+ }))
493
+ }
494
+ }
495
+
496
+ static async _readTemplateAsJson(template) {
497
+ const content = await fs.promises.readFile(path.join(__dirname, 'template', template), 'utf-8')
498
+ return JSON.parse(content.toString())
499
+ }
500
+
501
+ static _toEqualIgnoreMeta(csn1, csn2) {
502
+ function toString(csn) {
503
+ return JSON.stringify(csn, (k, v) => {
504
+ if (v?.creator) {
505
+ // make sure it's the compiler meta tag
506
+ if (k === 'meta' && v.creator?.startsWith('CDS Compiler'))
507
+ return
508
+ }
509
+ return v
510
+ })
511
+ }
512
+
513
+ if (csn1 === csn2) {
514
+ return true
515
+ }
516
+ if (!csn1 || !csn2) {
517
+ return false
518
+ }
519
+ return toString(csn1) === toString(csn2)
520
+ }
521
+ }
522
+ module.exports = HanaModuleBuilder