@sap/cds-dk 9.4.3 → 9.6.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 (168) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/bin/compile/index.js +1 -1
  3. package/bin/env.js +3 -3
  4. package/bin/export.js +1 -1
  5. package/bin/help.js +0 -1
  6. package/bin/import.js +8 -3
  7. package/bin/repl.js +7 -11
  8. package/bin/up.js +159 -23
  9. package/bin/util/containerize.js +79 -25
  10. package/bin/version.js +4 -4
  11. package/lib/bind/k8s.js +1 -1
  12. package/lib/build/plugins/helm/files/templates/_deployment_helpers.tpl +1 -1
  13. package/lib/build/plugins/helm/index.js +1 -1
  14. package/lib/build/provider/hana/2migration.js +16 -24
  15. package/lib/build/provider/hana/index.js +1 -1
  16. package/lib/build/provider/hana/template/.hdiconfig-all +7 -1
  17. package/lib/build/provider/hana/template/package.json +1 -1
  18. package/lib/build/provider/internalBuildPlugin.js +15 -1
  19. package/lib/build/provider/java/index.js +12 -1
  20. package/lib/build/provider/mtx-sidecar/index.js +2 -1
  21. package/lib/build/provider/nodejs/index.js +2 -1
  22. package/lib/build/util.js +36 -19
  23. package/lib/compile/to_openapi.js +1 -5
  24. package/lib/import/common.js +9 -2
  25. package/lib/import/odata/ParserContext.js +1 -0
  26. package/lib/import/odata/v4parser.js +57 -25
  27. package/lib/import/openapi/importOpenAPI.js +6 -1
  28. package/lib/init/constants.js +1 -1
  29. package/lib/init/index.js +20 -6
  30. package/lib/init/merge.js +16 -0
  31. package/lib/init/projectReader.js +34 -8
  32. package/lib/init/registries/mta.js +2 -2
  33. package/lib/init/template/ams/index.js +3 -3
  34. package/lib/init/template/app-frontend/files/app-package.json.hbs +1 -7
  35. package/lib/init/template/app-frontend/files/ui5.yaml.hbs +21 -1
  36. package/lib/init/template/app-frontend/index.js +2 -6
  37. package/lib/init/template/application-logging/index.js +11 -16
  38. package/lib/init/template/approuter/files/mta.yaml.hbs +0 -2
  39. package/lib/init/template/approuter/index.js +13 -10
  40. package/lib/init/template/attachments/index.js +4 -2
  41. package/lib/init/template/audit-logging/index.js +4 -2
  42. package/lib/init/template/cloud-logging/index.js +6 -5
  43. package/lib/init/template/connectivity/index.js +2 -2
  44. package/lib/init/template/console/files/package.json +5 -0
  45. package/lib/init/template/console/index.js +25 -0
  46. package/lib/init/template/containerize/index.js +4 -6
  47. package/lib/init/template/data/as-json.js +18 -20
  48. package/lib/init/template/destination/index.js +4 -2
  49. package/lib/init/template/dynatrace/files/package.json.hbs +2 -2
  50. package/lib/init/template/dynatrace/index.js +4 -4
  51. package/lib/init/template/enterprise-messaging/files/mta.yaml.hbs +3 -1
  52. package/lib/init/template/enterprise-messaging/index.js +4 -2
  53. package/lib/init/template/enterprise-messaging-shared/index.js +4 -2
  54. package/lib/init/template/github-actions/files/workflows/cf.yaml.hbs +16 -1
  55. package/lib/init/template/github-actions/files/workflows/release.yaml.hbs +2 -2
  56. package/lib/init/template/github-actions/files/workflows/test.yaml.hbs +20 -0
  57. package/lib/init/template/github-actions/index.js +7 -10
  58. package/lib/init/template/hana/files/db/package.json +2 -2
  59. package/lib/init/template/hana/files/mta.yaml.hbs +0 -4
  60. package/lib/init/template/hana/index.js +7 -9
  61. package/lib/init/template/helm/index.js +8 -10
  62. package/lib/init/template/helm-unified-runtime/index.js +1 -4
  63. package/lib/init/template/html5-repo/files/app-package.json.hbs +1 -7
  64. package/lib/init/template/html5-repo/files/ui5.yaml.hbs +21 -1
  65. package/lib/init/template/html5-repo/index.js +14 -18
  66. package/lib/init/template/ias/files/values.yaml.hbs +6 -6
  67. package/lib/init/template/ias/index.js +13 -21
  68. package/lib/init/template/java/index.js +14 -0
  69. package/lib/init/template/kafka/index.js +4 -2
  70. package/lib/init/template/{containerize → kyma}/files/containerize.yaml.hbs +17 -2
  71. package/lib/init/template/kyma/index.js +129 -5
  72. package/lib/init/template/malware-scanner/files/mta.yaml.hbs +3 -1
  73. package/lib/init/template/malware-scanner/files/values.yaml +3 -1
  74. package/lib/init/template/malware-scanner/index.js +4 -2
  75. package/lib/init/template/multitenancy/files/mta.yaml.hbs +1 -3
  76. package/lib/init/template/multitenancy/files/values.yaml.hbs +8 -0
  77. package/lib/init/template/multitenancy/index.js +15 -7
  78. package/lib/init/template/nodejs/files/.vscode/extensions.json +0 -3
  79. package/lib/init/template/nodejs/files/package.json.hbs +1 -1
  80. package/lib/init/template/pipeline/files/app-package.json.hbs +1 -7
  81. package/lib/init/template/pipeline/files/ui5.yaml.hbs +21 -1
  82. package/lib/init/template/pipeline/index.js +1 -2
  83. package/lib/init/template/portal/files/mta.yaml.hbs +5 -3
  84. package/lib/init/template/portal/index.js +8 -8
  85. package/lib/init/template/postgres/index.js +2 -2
  86. package/lib/init/template/redis/index.js +2 -3
  87. package/lib/init/template/redis-messaging/index.js +2 -3
  88. package/lib/init/template/sample/files/_i18n/i18n_de.properties +21 -0
  89. package/lib/init/template/sample/files/_i18n/i18n_en.properties +21 -0
  90. package/lib/init/template/sample/files/_i18n/i18n_fr.properties +21 -0
  91. package/lib/init/template/sample/files/_i18n/messages_de.properties +34 -0
  92. package/lib/init/template/sample/files/_i18n/messages_en.properties +34 -0
  93. package/lib/init/template/sample/files/_i18n/messages_fr.properties +34 -0
  94. package/lib/init/template/sample/files/app/_i18n/i18n.properties +13 -25
  95. package/lib/init/template/sample/files/app/_i18n/i18n_de.properties +13 -25
  96. package/lib/init/template/sample/files/app/admin-authors/fiori-service.cds +52 -0
  97. package/lib/init/template/sample/files/app/admin-authors/webapp/Component.js.hbs +3 -0
  98. package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n.properties +3 -0
  99. package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n_de.properties +3 -0
  100. package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n_en.properties +3 -0
  101. package/lib/init/template/sample/files/app/admin-authors/webapp/manifest.json +130 -0
  102. package/lib/init/template/sample/files/app/admin-books/fiori-service.cds +81 -88
  103. package/lib/init/template/sample/files/app/admin-books/webapp/Component.js.hbs +3 -0
  104. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n.properties +3 -3
  105. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n_de.properties +3 -3
  106. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n_en.properties +3 -3
  107. package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +24 -48
  108. package/lib/init/template/sample/files/app/appconfig/fioriSandboxConfig.json +56 -5
  109. package/lib/init/template/sample/files/app/browse/fiori-service.cds +30 -30
  110. package/lib/init/template/sample/files/app/browse/webapp/Component.js.hbs +3 -0
  111. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n.properties +3 -4
  112. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n_de.properties +3 -3
  113. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n_en.properties +3 -4
  114. package/lib/init/template/sample/files/app/browse/webapp/manifest.json +19 -24
  115. package/lib/init/template/sample/files/app/common.cds +127 -158
  116. package/lib/init/template/sample/files/app/fiori-apps.html +50 -0
  117. package/lib/init/template/sample/files/app/genres/fiori-service.cds +33 -0
  118. package/lib/init/template/sample/files/app/genres/tree-view.cds +42 -0
  119. package/lib/init/template/sample/files/app/genres/value-help.cds +6 -0
  120. package/lib/init/template/sample/files/app/genres/webapp/Component.js.hbs +3 -0
  121. package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n.properties +3 -0
  122. package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n_de.properties +3 -0
  123. package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n_en.properties +3 -0
  124. package/lib/init/template/sample/files/app/genres/webapp/manifest.json +126 -0
  125. package/lib/init/template/sample/files/app/services.cds +6 -2
  126. package/lib/init/template/sample/files/db/currencies.cds +13 -0
  127. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Books.csv +5 -5
  128. package/lib/init/template/sample/files/db/data/{sap.capire.bookshop-Books_texts.csv → sap.capire.bookshop-Books.texts.csv} +4 -4
  129. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Genres.csv +42 -15
  130. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Genres.texts.csv +43 -0
  131. package/lib/init/template/sample/files/db/data/sap.common-Currencies.csv +12 -0
  132. package/lib/init/template/sample/files/db/data/sap.common-Currencies.texts.csv +15 -0
  133. package/lib/init/template/sample/files/db/hana/index.cds +10 -0
  134. package/lib/init/template/sample/files/db/schema.cds +35 -22
  135. package/lib/init/template/sample/files/db/sqlite/index.cds +10 -0
  136. package/lib/init/template/sample/files/package.json.hbs +14 -1
  137. package/lib/init/template/sample/files/srv/access-control.cds +3 -0
  138. package/lib/init/template/sample/files/srv/admin-service.cds +3 -2
  139. package/lib/init/template/sample/files/srv/cat-service.cds +7 -11
  140. package/lib/init/template/sample/index.js +40 -18
  141. package/lib/init/template/sqlite/files/package.json.hbs +13 -0
  142. package/lib/init/template/sqlite/index.js +1 -1
  143. package/lib/init/template/telemetry/index.js +1 -3
  144. package/lib/init/template/typescript/files/package.json +1 -1
  145. package/lib/init/template/workzone-standard/files/mta.yaml.hbs +0 -5
  146. package/lib/init/template/workzone-standard/index.js +0 -1
  147. package/lib/init/template/xsuaa/files/mta.yaml.hbs +4 -0
  148. package/lib/init/template/xsuaa/index.js +7 -6
  149. package/lib/lint/Linter.js +10 -4
  150. package/lib/lint/eslintHelp.json +1 -1
  151. package/lib/mtx/tenant_action.js +12 -4
  152. package/lib/util/checksum.js +41 -0
  153. package/lib/util/fs.js +8 -1
  154. package/lib/util/glob.js +26 -13
  155. package/lib/util/question.js +60 -7
  156. package/npm-shrinkwrap.json +141 -118
  157. package/package.json +2 -2
  158. package/lib/init/template/app-frontend/files/ui5-deploy.yaml.hbs +0 -25
  159. package/lib/init/template/github-actions/files/cf-setup/action.yaml +0 -53
  160. package/lib/init/template/html5-repo/files/ui5-deploy.yaml.hbs +0 -25
  161. package/lib/init/template/pipeline/files/ui5-deploy.yaml.hbs +0 -25
  162. package/lib/init/template/sample/files/app/admin-books/webapp/Component.js +0 -8
  163. package/lib/init/template/sample/files/app/browse/webapp/Component.js +0 -7
  164. package/lib/init/template/sample/files/app/index.html +0 -32
  165. package/lib/init/template/sqlite/files/package.json +0 -5
  166. /package/lib/init/template/{helm → kyma}/files/Chart.yaml.hbs +0 -0
  167. /package/lib/init/template/{helm → kyma}/files/values.schema.json +0 -0
  168. /package/lib/init/template/{helm → kyma}/files/values.yaml.hbs +0 -0
@@ -2,7 +2,8 @@ const cds = require('../../lib/cds')
2
2
  const { path } = cds.utils
3
3
  const { join } = path
4
4
  const { execSync } = require('node:child_process')
5
- const { highlight } = require('../../lib/util/term')
5
+ const { highlight, info } = require('../../lib/util/term')
6
+ const { calcDirChecksum } = require('../../lib/util/checksum')
6
7
 
7
8
  const buildpacks = new Set([
8
9
  'java', 'nodejs', 'sap-machine', 'executable-jar', 'spring-boot', 'syft'
@@ -17,15 +18,20 @@ const keyProcessorMap = {
17
18
  buildpack: processBuildpack
18
19
  }
19
20
 
20
- const run = cmd => {
21
+ const run = (cmd, { continueOnFail = false, silent = false } = {}) => {
21
22
  cmd = Array.isArray(cmd) ? cmd.join(' ') : cmd
22
23
  console.log(highlight(cmd))
23
24
  try {
24
25
  return execSync(cmd, {
25
- stdio: 'inherit',
26
+ stdio: silent ? 'pipe' : 'inherit',
26
27
  env: { ...process.env, FORCE_COLOR: cds.utils.colors.enabled }
27
28
  })
28
29
  } catch (error) {
30
+ if (continueOnFail) {
31
+ if (!silent)
32
+ console.error(error.message)
33
+ return
34
+ }
29
35
  throw error.message
30
36
  }
31
37
  }
@@ -72,41 +78,46 @@ function processBuildpack(bp, name, tag, clearCache = false) {
72
78
  * @param {string} filename - The path to the YAML file to parse.
73
79
  * @param {string} [repoOpt=''] - Optional repository override. If not provided, uses the repository defined in the YAML file.
74
80
  * @param {boolean} [clearCache=false] - Whether to clear cache for buildpack builds.
75
- * @returns {{
81
+ * @returns {Promise<{
76
82
  * commands: Array<{
77
83
  * name: string,
78
84
  * tag: string,
79
85
  * buildCmd: Array<string>,
80
86
  * tagCmd: Array<string>,
81
- * pushCmd: Array<string>
87
+ * pushCmd: Array<string>,
88
+ * image: string,
89
+ * checksum: string|null
90
+ * path: string
82
91
  * }>,
83
92
  * before_all: Array<Array<string>>,
84
93
  * repository: string
85
- * }} An object containing the commands for each module, any global pre-commands, and the repository name.
94
+ * }>} An object containing the commands for each module, any global pre-commands, and the repository name.
86
95
  * @throws {Error} If modules are not defined in the YAML file or if build-parameters are invalid.
87
96
  */
88
- function parseYAML(filename, repoOpt = '', options) {
97
+ async function parseYAML(filename, repoOpt = '', options) {
89
98
  const file = cds.load.yaml(join(cds.root, filename))
90
99
  const { repository: repoFile, modules, tag: globalTag = 'latest' } = file
91
100
  const repository = repoOpt || repoFile
92
101
  const before_all = file['before-all'] ? file['before-all'].map(c => c.trim().split(' ')) : []
93
- if (!modules) throw new Error('Modules not defined')
94
- console.log(`Using repository: ${repository}`)
95
- const commands = modules.map(m => {
102
+ if (!modules) throw `no modules defined in ${filename}`
103
+ const commands = await Promise.all(modules.map(async (m) => {
96
104
  const name = m.name, tag = m.tag || globalTag, bp = m['build-parameters']
97
- const source = `${name}:${tag}`, repoImg = `${repository}/${source}`
105
+ const rawImg = `${repository}/${name}`, source = `${name}:${tag}`, repoImg = `${repository}/${source}`
98
106
  const key = Object.keys(bp)[0]
99
107
  const buildType = keyProcessorMap[key]
100
- if (!buildType) throw new Error(`build-parameters ${Object.keys(bp)} is invalid.`)
108
+
109
+ if (!buildType) throw `'build-parameters' ${Object.keys(bp)} in ${filename} is invalid`
101
110
  return {
102
- name, tag,
103
- buildCmd: key === 'buildpack'
104
- ? buildType(bp, name, tag, options.clearCache)
105
- : buildType(bp, name, tag),
106
- tagCmd: ['docker', 'tag', source, repoImg],
107
- pushCmd: ['docker', 'push', repoImg]
111
+ name,
112
+ tag,
113
+ buildCmd: buildType(bp, name, tag, key === 'buildpack' && options.clearCache),
114
+ pushCmd: ['docker', 'push', repoImg],
115
+ tagCmd: [['docker', 'tag', source, repoImg]],
116
+ image: rawImg,
117
+ source,
118
+ path: bp.buildpack?.path
108
119
  }
109
- })
120
+ }))
110
121
  return { commands, before_all, repository }
111
122
  }
112
123
 
@@ -117,26 +128,69 @@ function parseYAML(filename, repoOpt = '', options) {
117
128
  */
118
129
  async function executeModules(modules, options) {
119
130
  for (const m of modules) {
120
- console.log(`Containerising module ${m.name}`)
131
+ if (m.checksum && process.env.SKIP_CHECK != 'true') {
132
+ try {
133
+ run(`docker inspect --format='{{index .RepoTags 0}}' ${m.image}:${m.checksum}`, { silent: true })
134
+ console.log(info(`image ${m.image}:${m.checksum} already exists – skipping build and push`))
135
+ continue
136
+ } catch (error) {
137
+ // ignore
138
+ }
139
+ }
140
+
121
141
  for (const cmd of m.buildCmd) run(cmd)
122
142
  if (options.push) {
123
- run(m.tagCmd)
143
+ for (const cmd of m.tagCmd) run(cmd)
124
144
  run(m.pushCmd)
125
145
  }
126
146
  }
127
147
  }
128
148
 
149
+ /**
150
+ * Calculates checksums for the specified modules based on their directory contents.
151
+ * @param {*} modules - The list of modules to calculate checksums for.
152
+ * @param {string} filename - The filename to include in the checksum calculation.
153
+ */
154
+ async function calculateChecksums(modules, filename) {
155
+ for (const m of modules) {
156
+ if (m.path) {
157
+ m.checksum = await calcDirChecksum(
158
+ join(cds.root, m.path),
159
+ (dir) => !dir.includes('node_modules'),
160
+ 'sha256',
161
+ [
162
+ join(cds.root, filename),
163
+ join(cds.root, 'package.json')
164
+ ]
165
+ )
166
+ m.tagCmd = m.tagCmd
167
+ .concat(m.checksum ? [['docker', 'tag', m.source, `${m.image}:${m.checksum}`]] : [])
168
+ }
169
+ }
170
+ }
171
+
129
172
  /**
130
173
  * Main function to containerize modules based on a YAML configuration file.
131
174
  * @param {string} filename - The path to the YAML configuration file.
132
175
  * @param {{ push: boolean, clearCache: boolean }} options - Options object, e.g., { push: true } to push images after building.
133
- * @returns {Promise<void>}
176
+ * @returns {Promise<Array<{
177
+ * name: string,
178
+ * tag: string,
179
+ * buildCmd: Array<string>,
180
+ * tagCmd: Array<string>,
181
+ * pushCmd: Array<string>,
182
+ * image: string,
183
+ * checksum: string|null
184
+ * path: string
185
+ * }>} - The list of modules that were containerized.
134
186
  */
135
- async function containerize(filename, options = { push: false, clearCache: false }) {
136
- const { repository, before_all, commands: modules } = parseYAML(filename, '', options)
187
+ async function containerize(filename = 'containerize.yaml', options = { clearCache: !!process.env.CLEAR_CACHE, push: true }) {
188
+ const { repository, before_all, commands: modules } = await parseYAML(filename, '', options)
137
189
  if (!repository || !modules) return
138
- for (const cmd of before_all) await run(cmd)
190
+ for (const cmd of before_all) run(cmd)
191
+ await calculateChecksums(modules, filename)
139
192
  await executeModules(modules, options)
193
+ return modules
140
194
  }
141
195
 
142
196
  module.exports = { containerize }
package/bin/version.js CHANGED
@@ -52,7 +52,7 @@ function list_versions (args, options) {
52
52
 
53
53
  process.stdout.isTTY && console.log()
54
54
  if (options.markdown || options.info) {
55
- _markdown (versions)
55
+ _markdown (versions, options)
56
56
  } else {
57
57
  const mark = !colors ? s => s : require('../lib/util/term').info
58
58
  for (let each of Object.keys(versions)) console.log(`${mark(each)}: ${versions[each]}`)
@@ -81,7 +81,7 @@ function info({ root: cwd = process.cwd() }) {
81
81
 
82
82
  // Sort entries, with @sap/cds packages on top, followed by @cap-js packages
83
83
  const keys = Object.keys(versions).sort(), sorted={}
84
- for (let k of ['@sap/cds','@sap/cds-dk']) if (!(k in sorted)) sorted[k] = versions[k]
84
+ for (let k of ['@sap/cds']) if (!(k in sorted)) sorted[k] = versions[k]
85
85
  for (let k of keys) if (!(k in sorted) && k.startsWith('@sap/cds')) sorted[k] = versions[k]
86
86
  for (let k of keys) if (!(k in sorted) && k.startsWith('@cap-js')) sorted[k] = versions[k]
87
87
  for (let k of keys) if (!(k in sorted)) sorted[k] = versions[k]
@@ -115,9 +115,9 @@ function _versions_from(pkg_path, pkg_label) {
115
115
  return versions
116
116
  }
117
117
 
118
- function _markdown (versions) {
118
+ function _markdown (versions, { root: cwd = process.cwd() }) {
119
119
  const pkg = { name:'', repository:'', version:'' }; try {
120
- Object.assign (pkg, require (resolve('package.json')))
120
+ Object.assign (pkg, require (require.resolve('./package.json', { paths:[cwd] })))
121
121
  } catch (e) {/* ignored */}
122
122
  const repo = pkg.repository.url || pkg.repository || 'https://github.com/<your/repo>'
123
123
  const valLength = Math.max(repo.length, versions.home.length)
package/lib/bind/k8s.js CHANGED
@@ -32,7 +32,7 @@ module.exports = new class K8SBind extends require('./plugin') {
32
32
 
33
33
  services4(app) {
34
34
  const { execSync } = require('node:child_process')
35
- const json = cmd => JSON.parse(execSync(cmd, { encoding: 'utf8' }))
35
+ const json = cmd => JSON.parse(execSync(cmd, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 }))
36
36
 
37
37
  const bindings = json('kubectl get servicebindings -o json').items
38
38
  .filter(b => b.metadata.name.startsWith(app))
@@ -23,7 +23,7 @@ copied from web-application: web-application.fullname
23
23
  */}}
24
24
  {{- define "cap.deploymentHost" -}}
25
25
  {{- if .deployment.expose.host }}
26
- {{- .deployment.expose.host }}
26
+ {{- tpl .deployment.expose.host . }}
27
27
  {{- else }}
28
28
  {{- $name := (include "cap.web-application.fullname" .) }}
29
29
  {{- if hasPrefix $name .Release.Namespace }}
@@ -27,7 +27,7 @@ module.exports = class HelmBuildPlugin extends Plugin {
27
27
 
28
28
  async build() {
29
29
  if (!HelmBuildPlugin.hasTask()) {
30
- this.pushMessage("Helm Chart not found. Execute `cds add helm` to add the helm chart.", Plugin.WARNING)
30
+ this.pushMessage("Helm Chart not found. Execute `cds add kyma` to add the helm chart.", Plugin.WARNING)
31
31
  return
32
32
  }
33
33
 
@@ -48,31 +48,23 @@ function _compileToHana(model, options, beforeImage) {
48
48
  let migrations = []
49
49
  let afterImage
50
50
 
51
- if (cds.compile.to.hana) {
52
- const result = cds.compile.to.hana(model, options, beforeImage);
53
-
54
- for (const [content, { file }] of result) {
55
- switch (file) {
56
- case 'deletions.json':
57
- break
58
- case 'migrations.json':
59
- migrations = content
60
- break
61
- case 'afterImage.json':
62
- afterImage = content
63
- break
64
- default: {
65
- const { name, ext: suffix } = path.parse(file)
66
- definitions.push({ name, suffix, sql: content })
67
- }
51
+ const result = cds.compile.to.hana(model, options, beforeImage);
52
+
53
+ for (const [content, { file }] of result) {
54
+ switch (file) {
55
+ case 'deletions.json':
56
+ break
57
+ case 'migrations.json':
58
+ migrations = content
59
+ break
60
+ case 'afterImage.json':
61
+ afterImage = content
62
+ break
63
+ default: {
64
+ const { name, ext: suffix } = path.parse(file)
65
+ definitions.push({ name, suffix, sql: content })
68
66
  }
69
67
  }
70
- } else {
71
- // compatibility with cds < 8.0.0
72
- const result = cdsc.to.hdi.migration(cds.minify(model), options, beforeImage)
73
- definitions = result.definitions
74
- migrations = result.migrations
75
- afterImage = result.afterImage
76
68
  }
77
69
  let migrationTableNames = new Set()
78
70
  if (afterImage) {
@@ -97,7 +89,7 @@ function _compileToHana(model, options, beforeImage) {
97
89
  * @param {String} srcPath Fully qualified path of the directory containing .hdbmigrationtable files
98
90
  * @param {String} migration the migration descriptor
99
91
  * @param {String} tableSql SQL TABLE definition
100
- * @returns {Object} Providing access to 'content' and 'fileName'.
92
+ * @returns {Promise<Object>} Providing access to 'content' and 'fileName'.
101
93
  */
102
94
  async function _2migrationtable(srcPath, migration, tableSql) {
103
95
  let migrationTableModel = null
@@ -586,7 +586,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
586
586
  /**
587
587
  * Returns all hana native artifacts located in db/src/** workspace folders.
588
588
  * @param {Array} workspacePaths Array of workspace paths, 'cds.root' is excluded from the list
589
- * @returns {Array} Array of workspace files, each entry contains the db src directory,
589
+ * @returns {Promise<Array>} Array of workspace files, each entry contains the db src directory,
590
590
  * the relative file path and the file name.
591
591
  */
592
592
  async _getWorkspaceFiles(workspacePaths) {
@@ -167,6 +167,12 @@
167
167
  },
168
168
  "hdbschedulerjob" : {
169
169
  "plugin_name" : "com.sap.hana.di.schedulerjob"
170
+ },
171
+ "hdblogicalschema" : {
172
+ "plugin_name" : "com.sap.hana.di.logicalschema"
173
+ },
174
+ "hdblogicalschemaconfig" : {
175
+ "plugin_name" : "com.sap.hana.di.logicalschema.config"
170
176
  }
171
177
  }
172
- }
178
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "deploy",
3
3
  "dependencies": {
4
- "hdb": "^0",
4
+ "hdb": "^2.0.0",
5
5
  "@sap/hdi-deploy": "^5"
6
6
  },
7
7
  "engines": {
@@ -4,6 +4,7 @@ const cds = require('../../cds')
4
4
  const Plugin = require('../plugin')
5
5
  const { hasOptionValue, getFiles } = require('../util')
6
6
  const { FOLDER_GEN, DEFAULT_BUILTIN_PRIORITY_MAX } = require('../constants')
7
+ const TRACE = cds.debug('trace')
7
8
 
8
9
  class InternalBuildPlugin extends Plugin {
9
10
  /**
@@ -116,7 +117,7 @@ class InternalBuildPlugin extends Plugin {
116
117
  cwd: cds.root,
117
118
  src: this.hasBuildOption('flatDeployLayout', true) || this.task.src === this.task.dest ? this.task.src : cds.root
118
119
  }
119
- const csnStr = cds.compile.to.json(model, jsonOptions)
120
+ const csnStr = cds_compile_to_json(model, jsonOptions)
120
121
  await this.write(csnStr).to(csnFile)
121
122
  return csnStr
122
123
  }
@@ -156,4 +157,17 @@ class InternalBuildPlugin extends Plugin {
156
157
  return null
157
158
  }
158
159
  }
160
+
161
+ // Emits the 'compile.for.runtime' event, as the produced CSN is for runtime usage.
162
+ // Plugins use it to modify the CSN before it is stringified.
163
+ function cds_compile_to_json (csn,o) {
164
+ TRACE?.time('cds.compile 2json'.padEnd(22)); try {
165
+ let result, next = ()=> result ??= cds.compile.to.json (csn,o)
166
+ cds.emit ('compile.for.runtime', csn, o, next)
167
+ return next() //> in case no handler called next
168
+ }
169
+ finally { TRACE?.timeEnd('cds.compile 2json'.padEnd(22)) }
170
+ }
171
+
172
+
159
173
  module.exports = InternalBuildPlugin
@@ -7,6 +7,7 @@ const { OUTPUT_MODE, OUTPUT_MODE_RESULT, FILE_EXT_CDS, CONTENT_LANGUAGE_BUNDLES,
7
7
  const { getI18nDefaultFolder } = require('../../util')
8
8
  const DEFAULT_COMPILE_DEST_FOLDER = path.normalize('src/main/resources/edmx')
9
9
  const DEBUG = cds.debug('cli|build')
10
+ const LOG = cds.log('cli|build')
10
11
 
11
12
  class JavaBuildPlugin extends EdmxBuildPlugin {
12
13
  init() {
@@ -25,7 +26,17 @@ class JavaBuildPlugin extends EdmxBuildPlugin {
25
26
 
26
27
  if (!this.hasBuildOption(CONTENT_EDMX, false)) {
27
28
  // generate edmx files containing all features - localized edmx for backward compatibility
28
- if (this.hasBuildOption(CONTENT_LOCALIZED_EDMX, true)) {
29
+ let localized = this.hasBuildOption(CONTENT_LOCALIZED_EDMX, true)
30
+
31
+ // Force localized to true for OData v2
32
+ if (cds.env.odata.version === 'v2') {
33
+ if (!localized) {
34
+ LOG.info('Forcing localized EDMX generation for OData v2')
35
+ localized = true
36
+ }
37
+ }
38
+
39
+ if (localized) {
29
40
  await this.compileToEdmx(model, compileDest, { [FLAVOR_LOCALIZED_EDMX]: true })
30
41
  }
31
42
  // non-localized edmx
@@ -86,7 +86,8 @@ class MtxSidecarBuildPlugin extends NodejsBuildPlugin {
86
86
  return
87
87
  }
88
88
  const { dictionary, sources } = await this.compileAll(model, destMainSrv, destMain)
89
- await this.collectAllLanguageBundles(dictionary, sources, destMainSrv, destMain)
89
+ // await this.collectAllLanguageBundles(dictionary, sources, destMainSrv, destMain)
90
+ await this.collectAllLanguageBundles(dictionary, sources, destMain, destMain)
90
91
 
91
92
  if (!this.hasBuildOption(CONTENT_EDMX, false)) {
92
93
  //inferred model expected by cds.compile.to.edmx()
@@ -53,7 +53,8 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
53
53
  if (model) {
54
54
  const { dictionary, sources } = await this.compileAll(model, destSrv, destRoot)
55
55
  // collect and write language bundles into single i18n.json file
56
- await this.collectAllLanguageBundles(dictionary, sources, destSrv, destRoot)
56
+ await this.collectAllLanguageBundles(dictionary, sources, destRoot, destRoot)
57
+ // await this.collectAllLanguageBundles(dictionary, sources, destSrv, destRoot)
57
58
 
58
59
  if (!this.hasBuildOption(CONTENT_EDMX, false)) {
59
60
  const compileOptions = { [FLAVOR_LOCALIZED_EDMX]: this.hasBuildOption(FLAVOR_LOCALIZED_EDMX, true) }
package/lib/build/util.js CHANGED
@@ -1,13 +1,19 @@
1
1
  // @ts-check
2
- const fs = require('fs')
3
- const path = require('path')
2
+ const cp = require('node:child_process');
3
+ const fs = require('node:fs')
4
+ const path = require('node:path')
5
+
4
6
  const cds = require('../cds')
5
- const cp = require('child_process');
6
- const execAsync = require('util').promisify(cp.exec)
7
- const IS_WIN = require('os').platform() === 'win32'
7
+ const execAsync = require('node:util').promisify(cp.exec)
8
+ const { isRoot } = require('../util/fs')
9
+
10
+ const IS_WIN = require('node:os').platform() === 'win32'
8
11
  const { SEVERITY_ERROR, FILE_EXT_CDS, NODEJS_MODEL_EXCLUDE_LIST } = require('./constants');
9
12
  const DEBUG = cds.debug('cli|build')
10
- const WS_MAX_DEPTH = 2
13
+
14
+ // failsafe to avoid too deep recursions
15
+ // increased from 2 to 5, adding a config options in the future
16
+ const WS_MAX_DEPTH = 5
11
17
 
12
18
  /** @typedef {{col?: number, endCol?: number, line?: number, endLine?: number, file: string}} SourceLocation */
13
19
  /** @typedef {object[] | object} Credentials */
@@ -165,9 +171,9 @@ function flatten(modelPaths) {
165
171
 
166
172
  function getI18nDefaultFolder() {
167
173
  // keep '_i18n' as preferred folder for compatibility reasons
168
- if (cds.env.i18n.folders.includes('_i18n')) return '_i18n'
169
- if (cds.env.i18n.folders.includes('i18n')) return 'i18n'
170
- return cds.env.i18n.folders[0] ?? 'i18n'
174
+ // if (cds.env.i18n.folders.includes('_i18n')) return '_i18n'
175
+ // if (cds.env.i18n.folders.includes('i18n')) return 'i18n'
176
+ return cds.env.i18n.folders?.[0] ?? '_i18n'
171
177
  }
172
178
 
173
179
  async function pathExists(path) {
@@ -279,22 +285,32 @@ async function getWorkspaces(wsRoot, resolve = false, devDependencies = true, vi
279
285
  }
280
286
 
281
287
  function findWorkspaceRoot(currentPath, depth = 0) {
282
- if (depth > WS_MAX_DEPTH) {
283
- DEBUG?.(`No workspaces exist, max depth of ${WS_MAX_DEPTH} reached`)
288
+ if (isRoot(currentPath) || depth > WS_MAX_DEPTH) {
289
+ DEBUG?.(`No workspaces found, filesystem root or max depth of ${WS_MAX_DEPTH} reached`)
284
290
  return
285
291
  }
286
- const packageJson = path.join(currentPath, 'package.json')
287
- if (fs.existsSync(packageJson)) {
288
- if (require(path.join(currentPath, 'package.json')).workspaces) {
292
+
293
+ const packageJsonPath = path.join(currentPath, 'package.json')
294
+ try {
295
+ const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8')
296
+ const packageJson = JSON.parse(packageJsonContent)
297
+ if (packageJson.workspaces?.length > 0) {
289
298
  return currentPath
290
299
  }
300
+ } catch (e) {
301
+ if (e.code !== 'ENOENT') {
302
+ if (e.name === 'SyntaxError') {
303
+ throw `Corrupt package.json found at ${packageJsonPath}: ${e.message}`
304
+ }
305
+ throw e
306
+ }
291
307
  }
308
+
292
309
  if (fs.existsSync(path.join(currentPath, '.gitmodules'))) {
293
310
  return // project root reached
294
311
  }
295
- if (path.dirname(currentPath) !== currentPath) {
296
- return findWorkspaceRoot(path.dirname(currentPath), depth + 1)
297
- }
312
+
313
+ return findWorkspaceRoot(path.dirname(currentPath), depth + 1)
298
314
  }
299
315
 
300
316
  /**
@@ -415,15 +431,16 @@ function _filterModelPaths(modelPaths, filter = undefined) {
415
431
  * @param {string} srcDir - the directory to search for files
416
432
  * @param {(file: string) => boolean} [filter] - the filter function to apply to the found files
417
433
  * @param {string[]} files - the set of files to add the found files to
434
+ * @param {boolean} [ignoreSymlinks=false] - whether to ignore symlinks
418
435
  * @returns {string[]} files found in the given directory and its subdirectories
419
436
  */
420
- function getFiles(srcDir, filter, files = []) {
437
+ function getFiles(srcDir, filter, files = [], ignoreSymlinks = false) {
421
438
  function handleResource(/** @type {string} entry */ entry) {
422
439
  if (!filter || filter.call(this, entry)) {
423
440
  const stats = fs.lstatSync(entry)
424
441
  if (cds.utils.isdir(entry)) {
425
442
  getFiles(entry, filter, files)
426
- } else if (stats.isFile() || stats.isSymbolicLink()) {
443
+ } else if (stats.isFile() || (!ignoreSymlinks && stats.isSymbolicLink())) {
427
444
  files.push(entry)
428
445
  }
429
446
  }
@@ -1,8 +1,4 @@
1
1
  const cds = require('../cds')
2
- const { compile } = cds._local('@cap-js/openapi') // load a local cap-js/openapi (fallback to our own)
3
-
4
- function cds_compile_to_openapi (csn, options) {
5
- return compile(csn, options)
6
- }
2
+ const { compile: cds_compile_to_openapi } = cds._local('@cap-js/openapi') // load a local cap-js/openapi (fallback to our own)
7
3
 
8
4
  module.exports = cds_compile_to_openapi
@@ -357,7 +357,13 @@ async function _updateNodeConfig(dest, services, kind, cwd, options) {
357
357
  config = {};
358
358
  options.config.trim().split(',').forEach(kv => {
359
359
  const [key, val] = kv.split('=');
360
- config[key] = val;
360
+ if (val === 'true' || val === 'false') {
361
+ config[key] = (val === 'true');
362
+ } else if (!isNaN(val)) {
363
+ config[key] = Number(val);
364
+ } else {
365
+ config[key] = val;
366
+ }
361
367
  })
362
368
  normalizeToDeepObject(config);
363
369
  }
@@ -510,7 +516,8 @@ async function postProcess(filePath, options, csn, cwd) {
510
516
  usingStatements.push(`using from './${path.basename(csnFile.replace(`'`, `''`))}';`);
511
517
  }
512
518
 
513
- _printUsageHint(csnFile, services, cwd);
519
+ if (!options.dry)
520
+ _printUsageHint(csnFile, services, cwd);
514
521
 
515
522
  if (!options.dry && !options.no_copy)
516
523
  await _copyToSrvExternal(filePath, destFilePath, cwd);
@@ -38,6 +38,7 @@ module.exports = class ParserContext {
38
38
  this.schemaAliasToNamespace = {};
39
39
  this.schemaToSchemaDataIndex = {};
40
40
  this.schemaToPrimarySchema = {};
41
+ this.schemaDetails = {};
41
42
  this.vocabNamespaceToAlias = {};
42
43
  this.entityToBoundActions = {};
43
44
  this.entityToBoundFunctions = {};