@sap/cds-dk 9.5.0 → 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 (88) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/bin/env.js +3 -3
  3. package/bin/up.js +22 -16
  4. package/bin/util/containerize.js +27 -14
  5. package/lib/build/util.js +33 -16
  6. package/lib/import/common.js +7 -1
  7. package/lib/import/odata/v4parser.js +25 -38
  8. package/lib/init/constants.js +1 -1
  9. package/lib/init/index.js +5 -0
  10. package/lib/init/projectReader.js +19 -8
  11. package/lib/init/template/ams/index.js +2 -2
  12. package/lib/init/template/app-frontend/files/ui5.yaml.hbs +1 -0
  13. package/lib/init/template/app-frontend/index.js +2 -2
  14. package/lib/init/template/application-logging/index.js +2 -2
  15. package/lib/init/template/approuter/index.js +9 -5
  16. package/lib/init/template/attachments/index.js +4 -2
  17. package/lib/init/template/audit-logging/index.js +4 -2
  18. package/lib/init/template/cloud-logging/index.js +4 -2
  19. package/lib/init/template/connectivity/index.js +2 -2
  20. package/lib/init/template/console/files/package.json +5 -0
  21. package/lib/init/template/console/index.js +25 -0
  22. package/lib/init/template/containerize/index.js +4 -2
  23. package/lib/init/template/destination/index.js +4 -2
  24. package/lib/init/template/dynatrace/files/package.json.hbs +2 -2
  25. package/lib/init/template/dynatrace/index.js +4 -2
  26. package/lib/init/template/enterprise-messaging/files/mta.yaml.hbs +3 -1
  27. package/lib/init/template/enterprise-messaging/index.js +4 -2
  28. package/lib/init/template/enterprise-messaging-shared/index.js +4 -2
  29. package/lib/init/template/github-actions/files/workflows/release.yaml.hbs +2 -2
  30. package/lib/init/template/github-actions/index.js +4 -4
  31. package/lib/init/template/hana/index.js +4 -4
  32. package/lib/init/template/helm/index.js +6 -3
  33. package/lib/init/template/helm-unified-runtime/index.js +1 -0
  34. package/lib/init/template/html5-repo/files/ui5.yaml.hbs +1 -0
  35. package/lib/init/template/html5-repo/index.js +9 -7
  36. package/lib/init/template/ias/index.js +2 -2
  37. package/lib/init/template/java/index.js +14 -0
  38. package/lib/init/template/kafka/index.js +4 -2
  39. package/lib/init/template/kyma/index.js +124 -13
  40. package/lib/init/template/malware-scanner/index.js +4 -2
  41. package/lib/init/template/multitenancy/index.js +7 -4
  42. package/lib/init/template/nodejs/files/.vscode/extensions.json +0 -3
  43. package/lib/init/template/pipeline/files/ui5.yaml.hbs +1 -0
  44. package/lib/init/template/portal/index.js +6 -3
  45. package/lib/init/template/postgres/index.js +2 -2
  46. package/lib/init/template/redis/index.js +2 -3
  47. package/lib/init/template/redis-messaging/index.js +2 -3
  48. package/lib/init/template/sample/files/_i18n/i18n_de.properties +9 -0
  49. package/lib/init/template/sample/files/_i18n/i18n_fr.properties +1 -0
  50. package/lib/init/template/sample/files/app/_i18n/i18n.properties +17 -0
  51. package/lib/init/template/sample/files/app/_i18n/i18n_de.properties +16 -2
  52. package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n.properties +3 -11
  53. package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n_de.properties +3 -0
  54. package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n_en.properties +3 -0
  55. package/lib/init/template/sample/files/app/admin-authors/webapp/manifest.json +1 -1
  56. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n.properties +3 -11
  57. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n_de.properties +3 -0
  58. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n_en.properties +3 -0
  59. package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +1 -1
  60. package/lib/init/template/sample/files/app/appconfig/fioriSandboxConfig.json +6 -6
  61. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n.properties +3 -11
  62. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n_de.properties +3 -0
  63. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n_en.properties +3 -0
  64. package/lib/init/template/sample/files/app/browse/webapp/manifest.json +1 -1
  65. package/lib/init/template/sample/files/app/common.cds +1 -0
  66. package/lib/init/template/sample/files/app/fiori-apps.html +32 -12
  67. package/lib/init/template/sample/files/app/genres/fiori-service.cds +1 -1
  68. package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n.properties +3 -4
  69. package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n_de.properties +3 -2
  70. package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n_en.properties +3 -0
  71. package/lib/init/template/sample/files/app/genres/webapp/manifest.json +2 -2
  72. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Genres.texts.csv +43 -0
  73. package/lib/init/template/sample/index.js +14 -1
  74. package/lib/init/template/sqlite/files/package.json.hbs +13 -0
  75. package/lib/init/template/sqlite/index.js +1 -1
  76. package/lib/init/template/xsuaa/index.js +5 -2
  77. package/lib/lint/eslintHelp.json +1 -1
  78. package/lib/util/checksum.js +19 -10
  79. package/lib/util/fs.js +8 -1
  80. package/lib/util/glob.js +26 -13
  81. package/npm-shrinkwrap.json +101 -102
  82. package/package.json +1 -1
  83. package/lib/init/template/sample/files/app/_i18n/i18n_en.properties +0 -8
  84. package/lib/init/template/sqlite/files/package.json +0 -5
  85. /package/lib/init/template/{helm → kyma}/files/Chart.yaml.hbs +0 -0
  86. /package/lib/init/template/{containerize → kyma}/files/containerize.yaml.hbs +0 -0
  87. /package/lib/init/template/{helm → kyma}/files/values.schema.json +0 -0
  88. /package/lib/init/template/{helm → kyma}/files/values.yaml.hbs +0 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,35 @@ This project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  The format is based on [Keep a Changelog](https://keepachangelog.com/).
8
8
 
9
+ ## Version 9.6.0 - 2025-12-16
10
+
11
+ ### Added
12
+
13
+ - `cds add console` adds dependency to the CAP console plugin.
14
+
15
+ ### Changed
16
+
17
+ - `cds import` no longer generates a first parameter typed as `$self` to bound actions, as this parameter is implicit.
18
+ - `cds add` sets `relativePaths: true` for generated UI5 apps to avoid deployment issues if UI5 data sources start with a "/".
19
+ - `cds up` automatically detects the preferred platform.
20
+ - Added deprecation notices for the following commands in favor of using `cds add kyma`:
21
+ - `cds add containerize`
22
+ - `cds add helm`
23
+ - `cds add helm-unified-runtime`
24
+
25
+ ### Fixed
26
+
27
+ - `cds build --ws-pack` now checks folder depth of up to 5.
28
+ - `cds import --config param1=true,param2=0` now writes boolean and number values correctly to `package.json`.
29
+ - `cds add sample` sets the correct UI component in _\_appconfig/fioriSandboxConfig.json_.
30
+ - `cds add xsuaa` correctly adds the authentication binding to the application deployer if executed afterwards.
31
+ - `cds env` prints coloured output by default again.
32
+ - `cds add java` will preserve existing _package.json_ settings.
33
+ - `cds add multitenancy` now correctly adds a `devDependencies` section when missing.
34
+ - `cds add mta` adds fixes an issue where a faulty XSUAA instance was added with `messaging: true` configured.
35
+ - `cds add sqlite --for production` correctly activates SQLite for production, not just development.
36
+ - `cds up -2 k8s` correctly runs `before_all` before the checksum calculation to calculate against the correct artifact.
37
+
9
38
  ## Version 9.5.0 - 2025-12-01
10
39
 
11
40
  ### Added
@@ -372,6 +401,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
372
401
  - `cds add ams` for Java adds a custom builder to the `mta.yaml` to circumvent the missing `srv/src/gen/policies`.
373
402
  - `cds add multitenancy` requires the `srv-api` instead of the `mtx-api` for Java projects.
374
403
 
404
+
405
+ ## Version 8.9.12 - 2025-12-16
406
+
407
+ ### Fixed
408
+
409
+ - `cds import` generates the on condition for a composition when the corresponding OData navigation property targets a collection, is contained (`$ContainsTarget` in OData EDM) and has a backlink (`$Partner` in OData EDM).
410
+ - `cds import` now considers OData contained navigation properties as compositions instead of associations.
411
+ - Support for `hdb` v2.
412
+
375
413
  ## Version 8.9.11 - 2025-11-21
376
414
 
377
415
  ### Fixed
package/bin/env.js CHANGED
@@ -67,12 +67,12 @@ async function env ([cmd, key, value], options={}) {
67
67
  env['_home_cds-dk'] = path.resolve(__dirname, '..') // to help tools find the DK
68
68
 
69
69
  if (!options.for && options.mocked) await cds.service.bindings
70
+ options.colors = cds.utils.colors.enabled === true
70
71
  return fn (env, key, value, options)
71
72
  }
72
73
 
73
74
 
74
75
  const { inspect, format } = require('node:util')
75
- const {colors} = require('../lib/util/term')
76
76
  const commands = {
77
77
 
78
78
  json: (env,k,v,o) => commands.get (env,k,v,{...o,json:true}),
@@ -88,7 +88,7 @@ const commands = {
88
88
  },
89
89
 
90
90
  get (conf, key, val, options) {
91
- let fmt = x => inspect (x, {depth:22, colors})
91
+ let fmt = x => inspect (x, { depth:22, colors: true })
92
92
 
93
93
  let obj = (
94
94
  !key ? conf
@@ -97,7 +97,7 @@ const commands = {
97
97
  : conf.get(key.replace(/^cds\./,''))
98
98
  )
99
99
 
100
- if (!colors) {
100
+ if (options.colors === false) {
101
101
  fmt = x => typeof x !== 'object' ? x : JSON.stringify(x,0,2)
102
102
  }
103
103
  if (options.json) {
package/bin/up.js CHANGED
@@ -13,7 +13,9 @@ module.exports = Object.assign(up, {
13
13
 
14
14
  *-2* | *--to* cf|k8s
15
15
 
16
- The platform to deploy to. Default is 'cf'.
16
+ The platform to deploy to.
17
+
18
+ Defaults to *k8s* when Helm charts are present and no *mta.yaml* exists, otherwise *cf*.
17
19
 
18
20
  *-n* | *--namespace* <name>
19
21
 
@@ -31,7 +33,7 @@ const { execSync } = require('node:child_process')
31
33
  const { tmpdir } = require('node:os')
32
34
  const crypto = require('node:crypto')
33
35
  const cds = require('../lib/cds')
34
- const { highlight, warn, dim } = require('../lib/util/term')
36
+ const { highlight, warn, dim, info } = require('../lib/util/term')
35
37
  const { exists, fs, path, path: { join }, read, write } = cds.utils
36
38
  const { ask4 } = require('../lib/util/question')
37
39
  const { readProject } = require('../lib/init/projectReader')
@@ -98,7 +100,8 @@ function writeStoredChecksum(appName, checksum) {
98
100
 
99
101
  async function up() {
100
102
  const project = readProject()
101
- const { apps, appName, hasApprouter, hasXsuaa, isMonorepoMicroservice, k8s } = project
103
+ const { apps, appName, hasApprouter, hasXsuaa, isMonorepoMicroservice, hasKyma, hasMta, k8s } = project
104
+ const toKyma = cds.cli.options.to === 'k8s' || hasKyma && !hasMta
102
105
 
103
106
  const html5DeployerPath = join(cds.root, cds.env.folders.app, 'html5-deployer', 'resources')
104
107
 
@@ -130,7 +133,7 @@ async function up() {
130
133
  }
131
134
  }
132
135
 
133
- if (cds.cli.options.to === 'k8s') {
136
+ if (toKyma) {
134
137
  if (!exists('chart') || !exists('containerize.yaml')) run('cds add kyma')
135
138
 
136
139
  if (project.hasHtml5Repo) {
@@ -138,9 +141,11 @@ async function up() {
138
141
  await mkdirp(html5DeployerPath)
139
142
  }
140
143
 
141
- for (const { app } of apps) {
144
+ for (const { app, archiveName } of apps) {
145
+ if (!archiveName) continue
146
+
142
147
  const appPath = path.join(cds.root, cds.env.folders.app, app)
143
- const zipPath = path.join(appPath, `dist/${app}.zip`)
148
+ const zipPath = path.join(appPath, `dist/${archiveName}`)
144
149
 
145
150
  // Calculate checksum for the app source folder (excluding node_modules and dist)
146
151
  const currentChecksum = await calcDirChecksum(
@@ -151,15 +156,14 @@ async function up() {
151
156
  const storedChecksum = readStoredChecksum(app)
152
157
  const needsBuild = currentChecksum !== storedChecksum || !exists(zipPath)
153
158
 
154
- if (needsBuild) {
159
+ if (needsBuild || process.env.SKIP_CHECK == 'true') {
155
160
  DEBUG?.(dim(`build required for '${app}' – checksums`, { old: storedChecksum, new: currentChecksum }))
156
161
  run(`npm run build --prefix ${appPath}`)
162
+ await copy(zipPath).to(html5DeployerPath, `${app}.zip`)
157
163
  writeStoredChecksum(app, currentChecksum)
158
164
  } else {
159
- console.log(highlight(`Skipping build for UI5 app '${app}' - no changes detected (checksum: ${currentChecksum.substring(0, 8)}...)`))
165
+ console.log(info(`Skipping build for UI5 app '${app}' - no changes detected (checksum: ${currentChecksum.substring(0, 8)}...)`))
160
166
  }
161
-
162
- await copy(zipPath).to(html5DeployerPath, `${app}.zip`)
163
167
  }
164
168
  }
165
169
  } else {
@@ -180,7 +184,7 @@ async function up() {
180
184
  if (!exists(target)) fs.symlinkSync(relative, target, 'junction')
181
185
  }
182
186
 
183
- if (cds.cli.options.to === 'k8s') {
187
+ if (toKyma) {
184
188
  const ns = cds.cli.options.namespace
185
189
  const nsHelm = ns ? `--namespace ${ns}` : ''
186
190
  const nsKubectl = ns ? `-n ${ns}` : ''
@@ -196,7 +200,7 @@ async function up() {
196
200
  }
197
201
 
198
202
  const isValidDomain = str => /^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(str)
199
- const { image, imagePullSecret } = k8s.values.global ?? {}
203
+ const { image, imagePullSecret } = k8s.values?.global ?? {}
200
204
  let registry = image?.registry
201
205
  if (!process.env.CI) {
202
206
  if (!registry || !isValidDomain(registry)) {
@@ -205,7 +209,9 @@ async function up() {
205
209
  if (yn?.toLowerCase() !== 'y' || process.env.CI) {
206
210
  throw `registry parameter is invalid, please specify it in ${valuesPath}`
207
211
  } else {
208
- registry = image.registry = await ask4(['registry server (e.g. index.docker.io): '])
212
+ registry = await ask4(['registry server (e.g. index.docker.io): '])
213
+ if (!k8s.values.global.image) k8s.values.global.image = {}
214
+ k8s.values.global.image.registry = registry
209
215
  const values = (await read(valuesPath)).replace(/(registry:\s*).*/, `$1${registry}`)
210
216
  await write(values).to(valuesPath)
211
217
 
@@ -217,15 +223,15 @@ async function up() {
217
223
  const imagePullSecretResult = run(`kubectl get secret ${imagePullSecret.name} ${nsKubectl} -o jsonpath="{.metadata.name}"`, { continueOnFail: true, silent: true })
218
224
  if (!imagePullSecretResult) {
219
225
  const yn = await ask4([`image pull secret '${imagePullSecret.name}' not found in namespace '${ns || 'default'}' – do you want to create it? (Y/n) `])
220
- if (yn?.toLowerCase() !== 'y' || process.env.CI) {
226
+ if (yn?.toLowerCase() !== 'y' && yn !== '') {
221
227
  throw `image pull secret '${imagePullSecret.name}' is required to pull images from the registry`
222
228
  } else {
223
229
  const [username, email] = await ask4([
224
230
  'registry username: ',
225
- 'registry email: ',
231
+ 'registry email: '
226
232
  ])
227
233
  const password = await ask4(['registry password: '], { redacted: true })
228
- run(`kubectl create secret docker-registry ${imagePullSecret.name} ${nsKubectl} --docker-server=${registry} --docker-username=${username} --docker-password='${password}' --docker-email=${email}`)
234
+ run(`kubectl create secret docker-registry ${imagePullSecret.name} ${nsKubectl} --docker-server=${registry} --docker-username=${username} --docker-password='${password}' --docker-email='${email}'`)
229
235
  }
230
236
  }
231
237
  }
@@ -104,17 +104,6 @@ async function parseYAML(filename, repoOpt = '', options) {
104
104
  const name = m.name, tag = m.tag || globalTag, bp = m['build-parameters']
105
105
  const rawImg = `${repository}/${name}`, source = `${name}:${tag}`, repoImg = `${repository}/${source}`
106
106
  const key = Object.keys(bp)[0]
107
- let checksum = null
108
- if (bp.buildpack?.path)
109
- checksum = await calcDirChecksum(
110
- join(cds.root, bp.buildpack.path),
111
- (dir) => !dir.includes('node_modules'),
112
- 'sha256',
113
- [
114
- join(cds.root, filename),
115
- join(cds.root, 'package.json')
116
- ]
117
- )
118
107
  const buildType = keyProcessorMap[key]
119
108
 
120
109
  if (!buildType) throw `'build-parameters' ${Object.keys(bp)} in ${filename} is invalid`
@@ -122,10 +111,10 @@ async function parseYAML(filename, repoOpt = '', options) {
122
111
  name,
123
112
  tag,
124
113
  buildCmd: buildType(bp, name, tag, key === 'buildpack' && options.clearCache),
125
- tagCmd: [['docker', 'tag', source, repoImg]].concat(checksum ? [['docker', 'tag', source, `${rawImg}:${checksum}`]] : []),
126
114
  pushCmd: ['docker', 'push', repoImg],
115
+ tagCmd: [['docker', 'tag', source, repoImg]],
127
116
  image: rawImg,
128
- checksum,
117
+ source,
129
118
  path: bp.buildpack?.path
130
119
  }
131
120
  }))
@@ -139,7 +128,7 @@ async function parseYAML(filename, repoOpt = '', options) {
139
128
  */
140
129
  async function executeModules(modules, options) {
141
130
  for (const m of modules) {
142
- if (m.checksum) {
131
+ if (m.checksum && process.env.SKIP_CHECK != 'true') {
143
132
  try {
144
133
  run(`docker inspect --format='{{index .RepoTags 0}}' ${m.image}:${m.checksum}`, { silent: true })
145
134
  console.log(info(`image ${m.image}:${m.checksum} already exists – skipping build and push`))
@@ -157,6 +146,29 @@ async function executeModules(modules, options) {
157
146
  }
158
147
  }
159
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
+
160
172
  /**
161
173
  * Main function to containerize modules based on a YAML configuration file.
162
174
  * @param {string} filename - The path to the YAML configuration file.
@@ -176,6 +188,7 @@ async function containerize(filename = 'containerize.yaml', options = { clearCac
176
188
  const { repository, before_all, commands: modules } = await parseYAML(filename, '', options)
177
189
  if (!repository || !modules) return
178
190
  for (const cmd of before_all) run(cmd)
191
+ await calculateChecksums(modules, filename)
179
192
  await executeModules(modules, options)
180
193
  return modules
181
194
  }
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 */
@@ -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
  }
@@ -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
  }
@@ -262,8 +262,8 @@ function _captureAnnotationInCSN(targetString, annotationObject, csnDefsTargetOb
262
262
  }
263
263
 
264
264
  /**
265
- * Annotations need to be flattened in CSN. E.g.
266
- *
265
+ * Annotations need to be flattened in CSN. E.g.
266
+ *
267
267
  * @Core.ContentDisposition : {
268
268
  * Type : 'inline',
269
269
  * Filename: typedImageFileName
@@ -426,7 +426,7 @@ function _validateAnnotationNamespace(annotationTerm, vocabNamespaceToAlias) {
426
426
  if (aliasOrNamespace === "cds") {
427
427
  return
428
428
  }
429
-
429
+
430
430
  for (let alias of Object.values(known_vocabularies)) {
431
431
  if (alias === aliasOrNamespace) {
432
432
  return;
@@ -462,7 +462,7 @@ function _resolveAnnotationTerm(schemaDataAnnotation, parserContext) {
462
462
  }
463
463
  }
464
464
  /**
465
- * Case 3: Namespace doesn't have edmx:Reference. But it is present in known_vocabularies.
465
+ * Case 3: Namespace doesn't have edmx:Reference. But it is present in known_vocabularies.
466
466
  * Replacing it with standard alias.
467
467
  */
468
468
  for (const [namespace, alias] of Object.entries(known_vocabularies)) {
@@ -682,7 +682,7 @@ function _parseAssociationProperty(foreignReference, associationNode, parserCont
682
682
  associationProperty.cardinality.max = 1;
683
683
  }
684
684
 
685
- // Generate on condition in case $Partner exists, as that indicates a backlink with which the
685
+ // Generate on condition in case $Partner exists, as that indicates a backlink with which the
686
686
  // condition can be generated.
687
687
  // If not given convert managed associations and compositions in unmanaged with empty key to avoid
688
688
  // "generation" of keys, that do not exist in the external service.
@@ -942,21 +942,10 @@ function _generateCSNForBoundActionFunction(actionFunctionObject, kind, parserCo
942
942
  csnObj.params = {};
943
943
 
944
944
  for (let index = 0; index < parameters.length; index++) {
945
- let isCollection = parameters[index].$Collection;
946
- csnObj.params[parameters[index].$Name] = _parseParameterAndReturnTypeContent(parameters[index], parserContext);
947
- /**
948
- * binding parameter handling - only first parameter will contain the binding
949
- * parameter information, so only make change to the first parameter information.
950
- * NOTE: Edm.Stream can act as the binding parameter for action/function
951
- * We don't have any example for it, so not handled as of now
952
- */
953
- if (isBound && index === 0) {
954
- if (isCollection) {
955
- csnObj.params[parameters[index].$Name].items.type = '$self';
956
- }
957
- else {
958
- csnObj.params[parameters[index].$Name].type = '$self';
959
- }
945
+ // skip the first parameter for bound actions, which is the $self parameter
946
+ // that is only implicit in CSN
947
+ if (!isBound || index > 0) {
948
+ csnObj.params[parameters[index].$Name] = _parseParameterAndReturnTypeContent(parameters[index], parserContext);
960
949
  }
961
950
  }
962
951
  }
@@ -1349,7 +1338,7 @@ function _parseEntityContainer(csnDefs, entityContainerElements, parserContext)
1349
1338
  Object.keys(parserContext.entityContainerAnnotations[annotationTarget]).length > 0
1350
1339
  ) {
1351
1340
  entityContainerAnnotations[annotationTarget] = Object.assign(
1352
- entityContainerAnnotations[annotationTarget],
1341
+ entityContainerAnnotations[annotationTarget],
1353
1342
  possibleAnnotations
1354
1343
  );
1355
1344
  }
@@ -1452,7 +1441,7 @@ function _collectBoundActionFunction(isAction, currNamespace, elementName, eleme
1452
1441
 
1453
1442
  // binding parameter type should be within the available namespaces in the scope of a document.
1454
1443
  if (entityTypeNamespace !== schemaName && entityTypeNamespace !== parserContext.schemaAliasToNamespace[schemaName]) {
1455
-
1444
+
1456
1445
  // checking if the namespace is available in other schema of the document
1457
1446
  if (parserContext.schemaToSchemaDataIndex.hasOwnProperty(schemaName)) {
1458
1447
  entityTypeNamespace = schemaName;
@@ -1680,30 +1669,28 @@ function _generateCSN(edmxAsJson, ignorePersistenceSkip, parserContext, importer
1680
1669
 
1681
1670
  // Step 9: 2. Step for generating $ContainsTarget & $Partner links
1682
1671
  // The containing composition is already generated. Now the Keys: [] has to be removed
1683
- // on the backlink association because per OData spec, OData does not expose the foreign key fields
1672
+ // on the backlink association because per OData spec, OData does not expose the foreign key fields
1684
1673
  // used by the backlink and thus it is save to assume they must be generated for proper mocking
1685
- //
1686
- // This happens after Step 8 because that adjusts the names of entity container entities where
1674
+ //
1675
+ // This happens after Step 8 because that adjusts the names of entity container entities where
1687
1676
  // entity type and entity set differ in naming
1688
- for (const entity in csn.definitions) {
1689
- if (entity.type !== 'entity') continue;
1690
-
1691
- const containingCompositions = Object.keys(csn.definitions[entity].elements).filter(e =>
1692
- csn.definitions[entity].elements[e].type === 'cds.Composition' &&
1693
- csn.definitions[entity].elements[e].on
1677
+ for (const [eName, entity] of Object.entries(csn.definitions)) {
1678
+ if (entity.kind !== 'entity') continue;
1679
+ const containingCompositions = Object.keys(entity.elements).filter(e =>
1680
+ entity.elements[e].type === 'cds.Composition' &&
1681
+ entity.elements[e].on
1694
1682
  );
1695
1683
  for (const comp of containingCompositions) {
1696
- const compTarget = csn.definitions[csn.definitions[entity].elements[comp].target];
1697
- const backLink = Object.keys(compTarget.elements).find(e =>
1698
- compTarget.elements[e].type === 'cds.Association' &&
1699
- compTarget.elements[e].target === entity
1684
+ const compTarget = csn.definitions[entity.elements[comp].target];
1685
+ const backLink = Object.keys(compTarget.elements).find(e =>
1686
+ compTarget.elements[e].type === 'cds.Association' &&
1687
+ compTarget.elements[e].target === eName
1700
1688
  )
1701
- if (compTarget.elements[backLink].keys) {
1689
+ if (compTarget.elements[backLink]?.keys) {
1702
1690
  delete compTarget.elements[backLink].keys
1703
1691
  }
1704
1692
  }
1705
1693
  }
1706
-
1707
1694
  return JSON.stringify(csn, null, 4);
1708
1695
  }
1709
1696
 
@@ -1715,7 +1702,7 @@ function _generateEDMX2JSON(edmx, name_space) {
1715
1702
  const v4Conv = MetadataConverterFactory.createEdmxV40XmlToV40Json(name_space);
1716
1703
  v4Conv.execute(inputBuffer, (error, json, missingReferencedDocuments) => {
1717
1704
  if (missingReferencedDocuments?.length > 0) {
1718
- console.log(missingReferencedDocuments);
1705
+ console.log(missingReferencedDocuments);
1719
1706
  }
1720
1707
 
1721
1708
  if (json) {
@@ -5,7 +5,7 @@ module.exports = {
5
5
  COMMAND_ADD: 'add',
6
6
 
7
7
  JAVA_LTS_VERSIONS: [17, 21],
8
- MAVEN_ARCHETYPE_VERSION: '4.4.2',
8
+ MAVEN_ARCHETYPE_VERSION: '4.6.0',
9
9
 
10
10
  OPTIONS: Object.freeze({
11
11
  COMPLETION: 'completion',
package/lib/init/index.js CHANGED
@@ -230,6 +230,11 @@ module.exports = class CDSGenerator {
230
230
  async _fillTemplateList(plugins) {
231
231
  const templates = new Map
232
232
  for (let plugin of plugins) {
233
+ // cds 10 remove
234
+ if(plugin === "helm" || plugin === "helm-unified-runtime") {
235
+ cds.cli.options.add?.add("kyma")
236
+ cds.cli.argv[0] += ",kyma"
237
+ }
233
238
  // Compat
234
239
  if (plugin === 'helm' && cds.cli.options['internal-unified-runtime-charts']) {
235
240
  console.warn(term.warn('"--internal-unified-runtime-charts" is deprecated, please use "cds add kyma --unified-runtime" instead'))
@@ -125,14 +125,17 @@ module.exports = new class ProjectReader {
125
125
  const vizId = firstInbound.semanticObject + '-' + firstInbound.action
126
126
  // Try to read archiveName from ui5.yaml if it exists
127
127
  let archiveName = ''
128
- const ui5DeployPath = path.resolve(cds.root, cds.env.folders.app, app, 'ui5.yaml')
129
- if (exists(ui5DeployPath)) {
130
- const ui5Deploy = cds.load.yaml(ui5DeployPath)
131
- const customTasks = ui5Deploy?.builder?.customTasks
132
- if (Array.isArray(customTasks)) {
133
- const zipperTask = customTasks.find(t => t.name === 'ui5-task-zipper')
128
+ const ui5YamlPaths = [
129
+ path.resolve(cds.root, cds.env.folders.app, app, 'ui5-deploy.yaml'),
130
+ path.resolve(cds.root, cds.env.folders.app, app, 'ui5.yaml')
131
+ ]
132
+ for (const yamlPath of ui5YamlPaths) {
133
+ if (exists(yamlPath)) {
134
+ const ui5Deploy = cds.load.yaml(yamlPath)
135
+ const zipperTask = ui5Deploy?.builder?.customTasks?.find?.(t => t.name === 'ui5-task-zipper')
134
136
  if (zipperTask?.configuration?.archiveName) {
135
137
  archiveName = `${zipperTask.configuration.archiveName}.zip`
138
+ break
136
139
  }
137
140
  }
138
141
  }
@@ -190,7 +193,14 @@ module.exports = new class ProjectReader {
190
193
  values: values ?? {},
191
194
  containerize: containerize ?? {}
192
195
  }
193
- }
196
+ },
197
+ hasHelmUnifiedRuntime: () =>
198
+ exists(join(cds.root, 'chart', 'Chart.yaml')) &&
199
+ (() => {
200
+ const chartYaml = cds.load.yaml(join(cds.root, 'chart', 'Chart.yaml'))
201
+ return chartYaml.annotations?.['app.kubernetes.io/managed-by'] === 'cds-dk/helm'
202
+ && chartYaml.dependencies?.find(dep => dep.name === 'web-application')?.repository !== undefined
203
+ })()
194
204
  }
195
205
 
196
206
  // Automatically creates availability checks for `cds add` commands.
@@ -302,6 +312,7 @@ function _normalizeSpringBootCfg(obj) {
302
312
  * @property {boolean} hasTyper Indicates if code supports CDS typer.
303
313
  * @property {boolean} hasHelmUnifiedRuntime Indicates if the project uses Unified Runtime Helm deployment.
304
314
  * @property {boolean} hasHelm Indicates if the project uses CAP Helm deployment.
315
+ * @property {boolean} hasKyma Indicates if the project uses Kyma.
305
316
  * @property {boolean} hasMta Indicates if the project uses MTA deployment.
306
317
  * @property {boolean} hasConnectivity Indicates if the project uses connectivity.
307
318
  * @property {boolean} hasMultitenancy Indicates if the project is multitenant.
@@ -326,4 +337,4 @@ function _normalizeSpringBootCfg(obj) {
326
337
  * @property {String} githubSlug GitHub org/repo.
327
338
  * @property {boolean} isMonorepoMicroservice Indicates if the project is a monorepo microservice.
328
339
  * @property {String} npmWorkspaceRoot The root path of the monorepo workspace.
329
- */
340
+ */
@@ -28,7 +28,7 @@ module.exports = class AmsTemplate extends require('../../plugin') {
28
28
 
29
29
  async combine() {
30
30
  const project = readProject()
31
- const { addMta, addCfManifest, addHelm, addHelmUnifiedRuntime, addContainerize } = project
31
+ const { addMta, addCfManifest, addContainerize, addKyma } = project
32
32
 
33
33
  if (addMta) {
34
34
  await this.#combineWithMta(project)
@@ -38,7 +38,7 @@ module.exports = class AmsTemplate extends require('../../plugin') {
38
38
  await this.#combineWithCfManifest(project)
39
39
  }
40
40
 
41
- if (addHelm || addHelmUnifiedRuntime) {
41
+ if (addKyma) {
42
42
  await this.#combineWithHelm(project)
43
43
  }
44
44
 
@@ -17,6 +17,7 @@ builder:
17
17
  afterTask: generateVersionInfo
18
18
  configuration:
19
19
  archiveName: {{app}}
20
+ relativePaths: true
20
21
  additionalFiles:
21
22
  - xs-app.json
22
23
  {{#isTypescript}}
@@ -79,14 +79,14 @@ module.exports = class extends require('../../plugin') {
79
79
  }
80
80
 
81
81
  // When Helm support is added
82
- // if (hasContainerize) {
82
+ // if (hasContainerize || hasKyma) {
83
83
  // const additions = [{
84
84
  // in: 'modules',
85
85
  // where: { name: `${project.appName}-content-deployer` }
86
86
  // }]
87
87
  // await merge(__dirname, '/files/containerize.yaml.hbs').into('containerize.yaml', { with: project, additions })
88
88
  // }
89
- // if (hasHelm || hasHelmUnifiedRuntime) {
89
+ // if (hasKyma) {
90
90
  // await renderAndCopy(join(__dirname, 'files/ui-resources'), 'ui-resources', project)
91
91
  // await merge(__dirname, 'files/values.yaml.hbs').into('chart/values.yaml', { with: project })
92
92
  // }
@@ -14,8 +14,8 @@ module.exports = class ApplicationLoggingTemplate extends require('../../plugin'
14
14
  }
15
15
 
16
16
  async canRun() {
17
- const { hasMta, hasHelm, hasHelmUnifiedRuntime } = readProject()
18
- if ((hasHelmUnifiedRuntime || hasHelm) && !hasMta) throw `'cds add application-logging' is not available for Kyma yet`
17
+ const { hasMta, hasKyma } = readProject()
18
+ if (hasKyma && !hasMta) throw `'cds add application-logging' is not available for Kyma yet`
19
19
  return true
20
20
  }
21
21