@sap/cds-dk 10.0.4 → 10.0.6

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 (62) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/debug.js +6 -2
  3. package/bin/export.js +1 -1
  4. package/bin/up.js +1 -1
  5. package/bin/version.js +7 -3
  6. package/bin/watch.js +1 -0
  7. package/lib/build/plugins/hana/index.js +19 -2
  8. package/lib/build/plugins/helm/files/templates/_deployment_helpers.tpl +2 -0
  9. package/lib/build/plugins/nodejs/index.js +6 -3
  10. package/lib/build/util.js +1 -1
  11. package/lib/init/projectReader.js +4 -2
  12. package/lib/init/template/ams/files/containerize.yaml.hbs +1 -0
  13. package/lib/init/template/ams/files/package.json.hbs +1 -1
  14. package/lib/init/template/approuter/files/values.yaml.hbs +9 -1
  15. package/lib/init/template/data/as-csv.js +4 -2
  16. package/lib/init/template/data/as-json.js +4 -1
  17. package/lib/init/template/github-actions/files/workflows/cf.yaml.hbs +6 -6
  18. package/lib/init/template/ias/files/values.yaml.hbs +4 -4
  19. package/lib/init/template/portal/files/values.yaml.hbs +12 -0
  20. package/lib/init/template/sample/files/app/browse/fiori-service.cds +2 -0
  21. package/lib/init/template/test/files/test/test.xs.hbs +3 -2
  22. package/lib/init/template/typer/files/xsconfig.json +1 -1
  23. package/lib/init/template/typescript/files/package.json +1 -1
  24. package/lib/init/template/xsuaa/index.js +11 -7
  25. package/lib/lint/eslintHelp.json +1 -1
  26. package/lib/watch/esm-resolve-registration.js +27 -0
  27. package/lib/watch/vite-watch.js +1 -1
  28. package/node_modules/@sap/cds/CHANGELOG.md +9 -0
  29. package/node_modules/@sap/cds/lib/env/defaults.js +1 -0
  30. package/node_modules/@sap/cds/lib/plugins.js +4 -2
  31. package/node_modules/@sap/cds/lib/srv/srv-handlers.js +1 -1
  32. package/node_modules/@sap/cds/libx/_runtime/common/Service.js +1 -1
  33. package/node_modules/@sap/cds/libx/_runtime/remote/Service.js +1 -1
  34. package/node_modules/@sap/cds/package.json +1 -1
  35. package/node_modules/@sap/cds-compiler/CHANGELOG.md +26 -0
  36. package/node_modules/@sap/cds-compiler/bin/cdsse.js +1 -1
  37. package/node_modules/@sap/cds-compiler/bin/cdsv2m.js +1 -1
  38. package/node_modules/@sap/cds-compiler/doc/IncompatibleChanges_v7.md +20 -13
  39. package/node_modules/@sap/cds-compiler/lib/base/builtins.js +4 -1
  40. package/node_modules/@sap/cds-compiler/lib/base/message-registry.js +15 -2
  41. package/node_modules/@sap/cds-compiler/lib/checks/defaultValues.js +0 -1
  42. package/node_modules/@sap/cds-compiler/lib/compiler/populate.js +1 -0
  43. package/node_modules/@sap/cds-compiler/lib/compiler/resolve.js +7 -6
  44. package/node_modules/@sap/cds-compiler/lib/compiler/tweak-assocs.js +27 -6
  45. package/node_modules/@sap/cds-compiler/lib/edm/annotations/edmJson.js +5 -0
  46. package/node_modules/@sap/cds-compiler/lib/json/from-csn.js +3 -3
  47. package/node_modules/@sap/cds-compiler/lib/json/to-csn.js +5 -4
  48. package/node_modules/@sap/cds-compiler/lib/model/csnUtils.js +13 -8
  49. package/node_modules/@sap/cds-compiler/lib/parsers/AstBuildingParser.js +14 -3
  50. package/node_modules/@sap/cds-compiler/lib/tool-lib/enrichCsn.js +1 -1
  51. package/node_modules/@sap/cds-compiler/lib/transform/db/cdsPersistence.js +3 -2
  52. package/node_modules/@sap/cds-compiler/lib/transform/db/views.js +2 -2
  53. package/node_modules/@sap/cds-compiler/lib/transform/transformUtils.js +1 -2
  54. package/node_modules/@sap/cds-compiler/package.json +1 -1
  55. package/node_modules/media-typer/README.md +15 -14
  56. package/node_modules/media-typer/index.js +0 -8
  57. package/node_modules/media-typer/package.json +16 -12
  58. package/node_modules/ws/lib/receiver.js +15 -32
  59. package/node_modules/ws/lib/websocket-server.js +4 -4
  60. package/node_modules/ws/lib/websocket.js +4 -4
  61. package/node_modules/ws/package.json +5 -1
  62. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -6,6 +6,43 @@ 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 10.0.6 - 2026-07-24
10
+
11
+ ### Added
12
+
13
+ - `cds add xsuaa` now generates namespace-suffixed XSUAA `role-collections` for Kyma.
14
+
15
+ ### Fixed
16
+
17
+ - `cds add http` no longer throws `MULTIPLE_DOCS` when `application.yaml` uses `---` separators (Spring Boot multi-profile format).
18
+ - `cds export --data` now reads seed data via in-memory SQLite instead of the configured db driver, so it no longer fails in projects with `requires.db: hana` (e.g. CAP Java).
19
+ - `cds init --java --add mta` now writes `path: srv` for the Java `srv` module in `mta.yaml` instead of `gen/srv`, so `mbt build` no longer fails with `the "gen/srv" path of the "<app>-srv" module does not exist`.
20
+ - `cds version` no longer reports dependency problems if no dependencies are installed at all. It prints an info message instead.
21
+ - `cds add test` creates tests that are green by default. Sample data is provided as commented code.
22
+
23
+ ## Version 10.0.5 - 2026-07-16
24
+
25
+ ### Fixed
26
+
27
+ - `cds build --for hana` now declares the plugin for native HANA artifacts placed in the build output (e.g. a `.hdbprojectionview` from `db/src`) in the generated `.hdiconfig`.
28
+ - `cds add typer` now generates a `jsconfig.json` file that works with VS Code.
29
+ - `cds debug <app> --force` on Kyma now preserves existing `NODE_OPTIONS` flags (e.g. `--max-old-space-size`) instead of overwriting them when enabling the inspector.
30
+ - `cds add sample` now selects `stock` in the Books list and object pages, so the overstock discount shows up in the UI.
31
+ - `cds add data` now generates correct foreign-key columns for nested compositions of aspects, e.g. `up__up__ID,up__ID,ID,name` instead of a broken `up__up_` header.
32
+ - `cds build` no longer fails with `npm ls -ws: unknown option` on npm 12, which dropped the short `-ws` flag.
33
+ - `cds build` now packages transitive npm workspace dependencies again on npm 12, which changed `npm pack --json` to emit an object keyed by package name instead of an array.
34
+ - `cds up` uses `helm --rollback-on-failure` instead of the deprecated `--atomic`.
35
+ - `cds add portal` on Java + IAS disables JWT proof-of-possession on `srv` (`SAP_SPRING_SECURITY_IDENTITY_PROOFTOKEN=false`) so Portal-proxied tile OData works.
36
+ - `cds add ias` forwards client certificates from the approuter to `srv-api` (`backendDestinations.srv-api.forwardAuthCertificates`).
37
+ - `cds add ias` always enables `xsuaa-cross-consumption` in the identity service parameters.
38
+ - `cds add ias` no longer sets an mTLS `expose.host` on the approuter, restoring a plain approuter host for the browser flow.
39
+ - `cds add portal` on Java multitenant now binds the Portal service instance to `srv` so CAP Java's `getDependencies` reports Portal to the SaaS Registry.
40
+ - `cds add kyma` uses a tenant-agnostic (tcpSocket) startup probe for the multitenant approuter, avoiding 400s from `TENANT_HOST_PATTERN` on kubelet probes.
41
+ - `cds add kyma` no longer emits the dead `Authentication: OAuth2UserTokenExchange` key on `backendDestinations.srv-api`.
42
+ - `cds add github-actions` uses updated versions.
43
+ - `cds watch` now works in ESM scenarios where only a global `cds-dk` is installed, where it would formerly fail with `ERR_MODULE_NOT_FOUND` for `@sap/cds`.
44
+ - `cds watch`no longer shows a `'server.hmr.protocol/host/port/path/clientPort/timeout/server' is deprecated` warning when running a Vite application.
45
+
9
46
  ## Version 10.0.4 - 2026-07-08
10
47
 
11
48
  ### Fixed
package/bin/debug.js CHANGED
@@ -101,7 +101,11 @@ async function debug() {
101
101
 
102
102
  if (force) {
103
103
  console.log(`\nEnabling Node inspector on deployment/${appName} at 0.0.0.0:${portLocal} ...`)
104
- _execSyncSafe(`kubectl ${ns} set env deployment/${appName} NODE_OPTIONS="--inspect=0.0.0.0:${portLocal}"`, { stdio: 'inherit' })
104
+ const existingOpts = _execSyncSafe(`kubectl ${ns} get deploy ${appName} -o jsonpath="{.spec.template.spec.containers[0].env[?(@.name=='NODE_OPTIONS')].value}"`)
105
+ const inspectFlag = `--inspect=0.0.0.0:${portLocal}`
106
+ const stripped = existingOpts ? existingOpts.replace(/--inspect(-brk)?(=\S+)?\s*/g, '').trim() : ''
107
+ const newOpts = stripped ? `${stripped} ${inspectFlag}` : inspectFlag
108
+ _execSyncSafe(`kubectl ${ns} set env deployment/${appName} NODE_OPTIONS="${newOpts.replace(/"/g, '\\"')}"`, { stdio: 'inherit' })
105
109
  _execSyncSafe(`kubectl ${ns} rollout restart deployment/${appName}`, { stdio: 'inherit' })
106
110
  k(`rollout status deployment/${appName} --timeout=180s`, { stdio: 'inherit' })
107
111
  } else {
@@ -281,7 +285,7 @@ async function debug() {
281
285
  return _execSync(...args)
282
286
  } catch { /* ignore */ }
283
287
  }
284
- function _execSync(cmd, options={ encoding: 'utf-8', shell: true }) {
288
+ function _execSync(cmd, options={ encoding: 'utf-8', shell: true, maxBuffer: 10 * 1024 * 1024 }) {
285
289
  try {
286
290
  return execSync(cmd, options)?.trim()
287
291
  } catch (err) {
package/bin/export.js CHANGED
@@ -200,7 +200,7 @@ class Export extends Minify {
200
200
  }
201
201
 
202
202
  async export_data (model) {
203
- const db = cds.db = await cds.deploy ('*',{silent:true})
203
+ const db = cds.db = await cds.deploy ('*',{silent:true}).to ('sqlite::memory:')
204
204
  const out = path.resolve (this.options.to, 'data')
205
205
  await fs.promises.rm (out, { recursive: true, force: true })
206
206
  await mkdirp (this.options.to, 'data')
package/bin/up.js CHANGED
@@ -346,7 +346,7 @@ async function up() {
346
346
  }
347
347
  })
348
348
 
349
- let helmUpgrade = `helm upgrade --install ${appName} ./gen/chart ${nsHelm} --atomic --wait --wait-for-jobs --timeout=10m`
349
+ let helmUpgrade = `helm upgrade --install ${appName} ./gen/chart ${nsHelm} --rollback-on-failure --wait --wait-for-jobs --timeout=20m`
350
350
  if (hasXsuaa) helmArgs.push(' --set-file xsuaa.jsonParameters=xs-security.json')
351
351
  if (helmArgs.length > 0) helmUpgrade += ` ${helmArgs.join(' ')}`
352
352
  run(helmUpgrade)
package/bin/version.js CHANGED
@@ -1,3 +1,4 @@
1
+ const cds = require('../lib')
1
2
 
2
3
  module.exports = exports = async function cds_version (pattern, options) {
3
4
  console.log()
@@ -9,7 +10,12 @@ module.exports = exports = async function cds_version (pattern, options) {
9
10
  print_versions (all.flat(),o)
10
11
  if (!o.json) {
11
12
  if (o.info) console.log ('\n','> Project:', project_info (o))
12
- if (o._elsp) console.log ('\n', `${RED}ELSPROBLEMS${RESET} reported by ${BOLD}npm ls${RESET}. Run ${BOLD}npm install${RESET} to fix dependency problems, then rerun ${BOLD}cds version${RESET}.`)
13
+ if (o._elsp) {
14
+ if (!cds.utils.fs.existsSync(join(o.root, 'node_modules')))
15
+ console.log (`\nNo dependencies installed. Run ${BOLD}npm install${RESET}, then rerun ${BOLD}cds version${RESET}.`)
16
+ else
17
+ console.log (`\n${RED}ELSPROBLEMS${RESET} reported by ${BOLD}npm ls${RESET}. Run ${BOLD}npm install${RESET} to fix dependency problems, then rerun ${BOLD}cds version${RESET}.`)
18
+ }
13
19
  }
14
20
  console.log()
15
21
  }
@@ -49,8 +55,6 @@ exports.help = `
49
55
  repository URL at the end as a markdown link.
50
56
  `
51
57
 
52
-
53
- const cds = require('../lib')
54
58
  const $ = cds.utils.sh || sh_polyfill
55
59
  const child_process = require('node:child_process')
56
60
  const home = require('os').homedir()
package/bin/watch.js CHANGED
@@ -148,6 +148,7 @@ async function watch ([cwd], {
148
148
  // if sap/cds is not installed locally, run with cds-dk's deps. (sap/cds, sqlite etc)
149
149
  if (!installed('@sap/cds', cwd)) {
150
150
  env.NODE_PATH = [...paths(cwd), ...paths(__dirname)].join(delimiter)
151
+ env.NODE_OPTIONS = [env.NODE_OPTIONS, `--import ${require.resolve('../lib/watch/esm-resolve-registration.js')}`].filter(Boolean).join(' ')
151
152
  }
152
153
 
153
154
  const delay = parseInt(process.env.CDS_WATCH_DELAY) || 200
@@ -68,7 +68,6 @@ module.exports = class HanaBuildPlugin extends require('../internal') {
68
68
  }
69
69
 
70
70
  // create additional stuff in dest
71
- await this._writeHdiConfig(hdiPlugins)
72
71
  await this._writeHdiNamespace()
73
72
  await this._writeUndeployJson()
74
73
 
@@ -100,6 +99,8 @@ module.exports = class HanaBuildPlugin extends require('../internal') {
100
99
  )
101
100
  }
102
101
 
102
+ await this._writeHdiConfig(hdiPlugins)
103
+
103
104
  // relocate .hdbsynonymconfig files containing *.configure properties to cfg/ folder
104
105
  // HDI's template mechanism for configuration files only works from the cfg/ folder
105
106
  // For inplace builds (Java), restrict the scan to compileDest only to avoid moving user's
@@ -433,7 +434,8 @@ module.exports = class HanaBuildPlugin extends require('../internal') {
433
434
  async _writeHdiConfig(compileHdiPlugins) {
434
435
  const DEFAULT_HDI_PLUGINS = [FILE_EXT_CSV, FILE_EXT_HDBTABLEDATA, FILE_EXT_HDBTABLE, ".hdbview", ".hdbindex", ".hdbconstraint", ".hdbcalculationview"]
435
436
  const undeployHdiPlugins = await this._readTypesFromUndeployJson() // see CAP issue #6222
436
- const hdiPlugins = new Set([...DEFAULT_HDI_PLUGINS, ...undeployHdiPlugins, ...compileHdiPlugins])
437
+ const destHdiPlugins = await this._readTypesFromCompileDest()
438
+ const hdiPlugins = new Set([...DEFAULT_HDI_PLUGINS, ...undeployHdiPlugins, ...compileHdiPlugins, ...destHdiPlugins])
437
439
  let hdiConfigPath
438
440
 
439
441
  // do not create the .hdiconfig file in 'gen/db' root folder for now
@@ -543,6 +545,21 @@ module.exports = class HanaBuildPlugin extends require('../internal') {
543
545
  return hdiPlugins
544
546
  }
545
547
 
548
+ async _readTypesFromCompileDest() {
549
+ const hdiPlugins = new Set()
550
+ const dir = path.join(this.task.dest, 'src')
551
+ if (!exists(dir)) return hdiPlugins
552
+
553
+ const template = await _readTemplateAsJson('.hdiconfig-all')
554
+ const knownSuffixes = new Set(Object.keys(template['file_suffixes']).map(key => '.' + key))
555
+ const files = await find(dir, { filter: (entry) => !isdir(entry) })
556
+ files.forEach(file => {
557
+ const suffix = path.extname(file)
558
+ if (knownSuffixes.has(suffix)) hdiPlugins.add(suffix)
559
+ })
560
+ return hdiPlugins
561
+ }
562
+
546
563
  async _validateMigrationTableFiles() {
547
564
  const dbSrcDir = path.join(this.task.src, "src")
548
565
  const migrationTableFiles = await find(dbSrcDir, { filter: (res) => {
@@ -88,6 +88,8 @@ Backend Destinations
88
88
  {{- end -}}
89
89
  {{- $currentDestination := dict $nameKey $destination.name $urlKey (print $protocol "://" $destinationHost ) -}}
90
90
  {{- $currentDestination := merge $currentDestination $.defaultParameters -}}
91
+ {{- $extras := omit $destination "name" "service" "external" -}}
92
+ {{- $currentDestination := merge $extras $currentDestination -}}
91
93
  {{- $destinations = (append $destinations $currentDestination) -}}
92
94
  {{- end -}}
93
95
  {{- $destinations | toJson }}
@@ -296,14 +296,17 @@ module.exports = class NodejsBuildPlugin extends require('../edmx') {
296
296
  * @param {import('fs').PathLike} dest - destination folder
297
297
  * @param {import('fs').PathLike} wsRoot - workspace root folder
298
298
  * @param {string[]} workspaces - list of workspace names
299
- * @returns {Promise<JSON>} - returns a promise that resolves to the result of the command
299
+ * @returns {Promise<object[]>} - array of pack descriptors ({ name, filename, ... })
300
300
  * @private
301
301
  */
302
302
  static async _execNpmPack(dest, wsRoot, workspaces) {
303
- const args = ['pack', '-ws', '--json', `--pack-destination=${dest}`]
303
+ const args = ['pack', '--ws', '--json', `--pack-destination=${dest}`]
304
304
  workspaces.forEach(w => args.push(`-w=${w}`))
305
305
  const result = await cmd.spawnCommand('npm', args, { cwd: wsRoot }, true, !!LOG._debug)
306
306
  DEBUG?.(result)
307
- return JSON.parse(result)
307
+ const parsed = JSON.parse(result)
308
+ // `npm pack --json` returns an array up to npm 11, but an object keyed by
309
+ // package name as of npm 12 — normalize to an array of descriptors.
310
+ return Array.isArray(parsed) ? parsed : Object.values(parsed)
308
311
  }
309
312
  }
package/lib/build/util.js CHANGED
@@ -300,7 +300,7 @@ async function _execNpmWs(wsRoot) {
300
300
  /** @type {string[]} */
301
301
  let result = []
302
302
  try {
303
- const cmdLine = 'npm ls -ws --json'
303
+ const cmdLine = 'npm ls --ws --json'
304
304
  DEBUG?.(`execute ${cmdLine}`)
305
305
  // @ts-expect-error - TODO: shell should be a string. But can probably be removed altogether?
306
306
  const { stdout } = await execAsync(cmdLine, { shell: IS_WIN, stdio: ['inherit', 'pipe', 'inherit'], cwd: wsRoot })
@@ -3,6 +3,7 @@ const { join, resolve, basename } = require('node:path')
3
3
  const { execSync } = require('node:child_process')
4
4
  const { fs, exists, isfile, isdir, path } = cds.utils
5
5
  const { parseXml } = require('./xml')
6
+ const YAML = require('yaml')
6
7
  const { REGEX_JAVA_VERSION, JAVA_LTS_VERSIONS } = require('./constants')
7
8
  const TRACE = cds.debug('trace')
8
9
 
@@ -69,6 +70,7 @@ module.exports = new class ProjectReader {
69
70
  readProject() {
70
71
  if (!this.appName) this.#setPackageInfo()
71
72
  const env = this.env4('production')
73
+ if (this.isJava) env.build.target = '.'
72
74
  const { appVersion, appName, appId, appDescription, jdkVersion } = this
73
75
  TRACE?.({ env })
74
76
 
@@ -249,8 +251,8 @@ module.exports._readSpringBootConfig = async function (roots = [cds.env.folders.
249
251
  for (let r of rootDirs) {
250
252
  const file = isfile(join(r, './src/main/resources/application.yaml'))
251
253
  if (file) {
252
- const yaml = cds.load.yaml(file)
253
- for (let yamlDoc of Array.isArray(yaml) ? yaml : [yaml]) {
254
+ const docs = YAML.parseAllDocuments(fs.readFileSync(file, 'utf-8'))
255
+ for (let yamlDoc of docs.map(d => d.toJS())) {
254
256
  let cds = yamlDoc?.cds;
255
257
  if (!cds) continue
256
258
  cds = _normalizeSpringBootCfg(cds)
@@ -17,4 +17,5 @@ modules:
17
17
  {{/isJava}}
18
18
  env:
19
19
  BP_NODE_RUN_SCRIPTS: ""
20
+ BP_NODE_VERSION: "24"
20
21
 
@@ -4,7 +4,7 @@
4
4
  "@sap/ams": "^3"
5
5
  },
6
6
  "devDependencies": {
7
- "@sap/ams-dev": "^2"
7
+ "@sap/ams-dev": "^3"
8
8
  },
9
9
  "engines": {
10
10
  "node": "^24"
@@ -16,11 +16,20 @@ approuter:
16
16
  ephemeral-storage: 1G
17
17
  cpu: 500m
18
18
  memory: 500M
19
+ {{^hasMultitenancy}}
19
20
  health:
20
21
  liveness:
21
22
  path: /
22
23
  readiness:
23
24
  path: /
25
+ {{/hasMultitenancy}}
26
+ {{#hasMultitenancy}}
27
+ health:
28
+ liveness:
29
+ path: ""
30
+ readiness:
31
+ path: ""
32
+ {{/hasMultitenancy}}
24
33
  {{=<% %>=}}
25
34
  envFrom:
26
35
  - configMapRef:
@@ -29,7 +38,6 @@ approuter:
29
38
  backendDestinations:
30
39
  srv-api:
31
40
  service: srv
32
- Authentication: OAuth2UserTokenExchange
33
41
  {{#hasMultitenancy}}
34
42
  mtx-api:
35
43
  service: {{#isJava}}srv{{/isJava}}{{^isJava}}sidecar{{/isJava}}
@@ -37,10 +37,12 @@ module.exports = async (data={}, csn, { headerOnly=false, separator=',' }={}) =>
37
37
  if (element.on) {
38
38
  continue // skip unmanaged associations with explicit on condition
39
39
  }
40
+ // flatten nested foreign keys (composite keys of nested aspects) into up__up__ID columns
41
+ const {headers: fkHeaders, records: fkRecords} = resolveStruct(value, name)
40
42
  if (i === 0) {
41
- header.push(...Object.keys(value).map(foreignKey => name + '_' + foreignKey))
43
+ header.push(...fkHeaders)
42
44
  }
43
- record.push(...Object.values(value))
45
+ record.push(...fkRecords)
44
46
  }
45
47
  else if (element.is_struct && element.type !== 'cds.Map') {
46
48
  const {headers: structHeaders, records: structRecords} = resolveStruct(value, name);
@@ -257,7 +257,10 @@ function dataForField(element, artifact, csn, record, data, parent, depth, refer
257
257
 
258
258
  const [ownKey, ownKeyStructured, targetKey] = findKeysInOnCondition(element, target)
259
259
  if (ownKey && targetKey) {
260
- const value = ownKeyStructured ? { [ownKey]: record[ownKey] } : record[ownKey]
260
+ // carry over all parent keys, not just the first, so composite/nested keys survive
261
+ const value = ownKeyStructured
262
+ ? Object.fromEntries(Object.keys(artifact.keys).map(k => [k, record[k]]))
263
+ : record[ownKey]
261
264
  targetRecords.forEach(targetRecord => {
262
265
  // Set parent in target as non-enumerable. Json serialization doesn't need it, but csv does.
263
266
  Object.defineProperty(targetRecord, targetKey, { value, enumerable: false })
@@ -44,13 +44,13 @@ jobs:
44
44
  echo "${PWD}/apache-maven-${MAVEN_VERSION}/bin" >> "$GITHUB_PATH"
45
45
  {{/sap}}
46
46
  {{/isJava}}
47
- - uses: cap-js/cf-setup@v1
47
+ - uses: cap-js/cf-setup@v2
48
48
  with:
49
- cf-api: {{#literal}}${{ vars.CF_API }}{{/literal}}
50
- cf-org: {{#literal}}${{ vars.CF_ORG }}{{/literal}}
51
- cf-space: {{#literal}}${{ vars.CF_SPACE }}{{/literal}}
52
- cf-username: {{#literal}}${{ vars.CF_USERNAME }}{{/literal}}
53
- cf-password: {{#literal}}${{ secrets.CF_PASSWORD }}{{/literal}}
49
+ api: {{#literal}}${{ vars.CF_API }}{{/literal}}
50
+ org: {{#literal}}${{ vars.CF_ORG }}{{/literal}}
51
+ space: {{#literal}}${{ vars.CF_SPACE }}{{/literal}}
52
+ username: {{#literal}}${{ vars.CF_USERNAME }}{{/literal}}
53
+ password: {{#literal}}${{ secrets.CF_PASSWORD }}{{/literal}}
54
54
  - run: npm install
55
55
  - run: npx cds up
56
56
 
@@ -13,14 +13,16 @@ sidecar:
13
13
  {{#hasApprouter}}
14
14
  approuter:
15
15
  expose:
16
- gateway: {{#literal}}"{{ .Release.Namespace }}/kyma-mtls-gateway"{{/literal}}
17
- host: {{#literal}}"{{ .Release.Name }}-approuter-{{ .Release.Namespace }}.cert"{{/literal}}
16
+ host: {{#literal}}"{{ .Release.Name }}-approuter-{{ .Release.Namespace }}"{{/literal}}
18
17
  bindings:
19
18
  auth:
20
19
  serviceInstanceName: identity
21
20
  parameters:
22
21
  credential-type: X509_GENERATED
23
22
  app-identifier: approuter
23
+ backendDestinations:
24
+ srv-api:
25
+ forwardAuthCertificates: true
24
26
  {{/hasApprouter}}
25
27
  srv:
26
28
  expose:
@@ -41,9 +43,7 @@ identity:
41
43
  {{#hasMultitenancy}}
42
44
  multi-tenant: true
43
45
  {{/hasMultitenancy}}
44
- {{#hasXsuaa}}
45
46
  xsuaa-cross-consumption: true
46
- {{/hasXsuaa}}
47
47
  provided-apis:
48
48
  - name: {{appName}}-ias-api
49
49
  description: API exposed by the application
@@ -6,9 +6,21 @@ approuter:
6
6
  portal:
7
7
  serviceOfferingName: portal
8
8
  servicePlanName: standard
9
+ {{#isJava}}{{#hasIas}}
10
+ srv:
11
+ env:
12
+ # Portal Service proxies tile OData without forwarding the client cert.
13
+ SAP_SPRING_SECURITY_IDENTITY_PROOFTOKEN: "false"
14
+ {{/hasIas}}{{/isJava}}
9
15
  {{#hasMultitenancy}}
10
16
  sidecar:
11
17
  bindings:
12
18
  portal:
13
19
  serviceInstanceName: portal
20
+ {{#isJava}}
21
+ srv:
22
+ bindings:
23
+ portal:
24
+ serviceInstanceName: portal
25
+ {{/isJava}}
14
26
  {{/hasMultitenancy}}
@@ -27,6 +27,7 @@ annotate CatalogService.Books with @(UI : {
27
27
  Value : currency.symbol,
28
28
  Label : '{i18n>Currency}'
29
29
  },
30
+ {Value : stock},
30
31
  ]},
31
32
  });
32
33
 
@@ -53,5 +54,6 @@ annotate CatalogService.Books with @(UI : {
53
54
  {Value : genre.name},
54
55
  {Value : price},
55
56
  {Value : currency.symbol},
57
+ {Value : stock},
56
58
  ]
57
59
  });
@@ -19,7 +19,7 @@ describe('{{serviceName}} OData APIs', () => {
19
19
  it('serves <%name%>', async () => {
20
20
  const { data } = await GET `<%&urlPath%> ${{ params: { $select: '<%&selectString%>' } }}`
21
21
  expect(data.value).to.containSubset([
22
- <%&elementsString%>,
22
+ // <%&elementsString%>,
23
23
  ])
24
24
  })
25
25
  <%={{ }}=%>{{/entities}}
@@ -27,7 +27,8 @@ describe('{{serviceName}} OData APIs', () => {
27
27
  {{#actions}}{{=<% %>=}}
28
28
  it('executes <%name%>', async () => {
29
29
  const { data } = await POST `<%&urlPath%> ${
30
- <%&paramsString%>
30
+ {}
31
+ // <%&paramsString%>
31
32
  }`
32
33
  // TODO finish this test
33
34
  // expect(data.value).to...
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "moduleResolution": "nodenext",
3
+ "module": "NodeNext",
4
4
  "paths": {
5
5
  "#cds-models/*": ["./@cds-models/*"],
6
6
  "@sap/cds": ["./node_modules/@cap-js/cds-types"]
@@ -3,6 +3,6 @@
3
3
  "@cap-js/cds-types": "^0.18.0",
4
4
  "@types/node": "^24.0.0",
5
5
  "tsx": "^4",
6
- "typescript": "^6.0.0"
6
+ "typescript": "^7.0.0"
7
7
  }
8
8
  }
@@ -86,13 +86,17 @@ module.exports = class XsuaaTemplate extends require('../../plugin') {
86
86
  if (addKyma || addMultitenancy && hasKyma) {
87
87
  const overwrites = addMultitenancy ? [{ item: 'xsuaa.parameters.tenant-mode', withValue: 'shared' }] : []
88
88
  await merge(__dirname, 'files/values.yaml.hbs').into('chart/values.yaml', { with: project, overwrites })
89
- await merge({
90
- xsuaa: {
91
- parameters: {
92
- xsappname: `${project.appName}-{{ .Release.Namespace }}`
93
- }
94
- }
95
- }).into('chart/values.yaml')
89
+ const parameters = {
90
+ xsappname: `${project.appName}-{{ .Release.Namespace }}`
91
+ }
92
+ if (!project.hasCustomRoleCollections && xsSecurity['role-templates'].length) {
93
+ parameters['role-collections'] = xsSecurity['role-templates'].map(role => ({
94
+ name: `${role.name} (${project.appName} {{ .Release.Namespace }})`,
95
+ description: 'generated',
96
+ 'role-template-references': [`$XSAPPNAME.${role.name}`],
97
+ }))
98
+ }
99
+ await merge({ xsuaa: { parameters } }).into('chart/values.yaml')
96
100
  await KymaTemplate.mergeDependency('service-instance', 'xsuaa')
97
101
 
98
102
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "10.6.0",
2
+ "version": "10.7.0",
3
3
  "help": "\n# SYNOPSIS\n\n *cds lint* [options] file.js [file.js] [dir]\n\n Runs environment checks and/or checks the specified models\n based on the ESLint framework\n\n# OPTIONS\n \n Basic configuration:\n --no-config-lookup Disable look up for eslint.config.js\n -c, --config path::String Use this configuration instead of eslint.config.js, eslint.config.mjs, or eslint.config.cjs\n --inspect-config Open the config inspector with the current configuration\n --ext [String] Specify additional file extensions to lint\n --global [String] Define global variables\n --parser String Specify the parser to be used\n --parser-options Object Specify parser options\n \n Specify Rules and Plugins:\n --plugin [String] Specify plugins\n --rule Object Specify rules\n \n Fix Problems:\n --fix Automatically fix problems\n --fix-dry-run Automatically fix problems without saving the changes to the file system\n --fix-type Array Specify the types of fixes to apply (directive, problem, suggestion, layout)\n \n Ignore Files:\n --no-ignore Disable use of ignore files and patterns\n --ignore-pattern [String] Patterns of files to ignore\n \n Use stdin:\n --stdin Lint code provided on <STDIN> - default: false\n --stdin-filename String Specify filename to process STDIN as\n \n Handle Warnings:\n --quiet Report errors only - default: false\n --max-warnings Int Number of warnings to trigger nonzero exit code - default: -1\n \n Output:\n -o, --output-file path::String Specify file to write report to\n -f, --format String Use a specific output format - default: stylish\n --color, --no-color Force enabling/disabling of color\n \n Inline configuration comments:\n --no-inline-config Prevent comments from changing config or rules\n --report-unused-disable-directives Adds reported errors for unused eslint-disable and eslint-enable directives\n --report-unused-disable-directives-severity String Chooses severity level for reporting unused eslint-disable and eslint-enable directives - either: off, warn, error, 0, 1, or 2\n --report-unused-inline-configs String Adds reported errors for unused eslint inline config comments - either: off, warn, error, 0, 1, or 2\n \n Caching:\n --cache Only check changed files - default: false\n --cache-file path::String Path to the cache file. Deprecated: use --cache-location - default: .eslintcache\n --cache-location path::String Path to the cache file or directory\n --cache-strategy String Strategy to use for detecting changed files in the cache - either: metadata or content - default: metadata\n \n Suppressing Violations:\n --suppress-all Suppress all violations - default: false\n --suppress-rule [String] Suppress specific rules\n --suppressions-location path::String Specify the location of the suppressions file\n --prune-suppressions Prune unused suppressions - default: false\n --pass-on-unpruned-suppressions Ignore unused suppressions - default: false\n \n Miscellaneous:\n --init Run config initialization wizard - default: false\n --env-info Output execution environment information - default: false\n --no-error-on-unmatched-pattern Prevent errors when pattern is unmatched\n --exit-on-fatal-error Exit with exit code 2 in case of fatal error - default: false\n --no-warn-ignored Suppress warnings when the file list includes ignored files\n --pass-on-no-patterns Exit with exit code 0 in case no file patterns are passed\n --debug Output debugging information\n -h, --help Show help\n -v, --version Output the version number\n --print-config path::String Print the configuration for the given file\n --stats Add statistics to the lint report - default: false\n --flag [String] Enable a feature flag\n --mcp Start the ESLint MCP server\n --concurrency Int|String Number of linting threads, auto to choose automatically, off for no multithreading - default: off\n \n",
4
4
  "options": [
5
5
  "--config",
@@ -0,0 +1,27 @@
1
+ // we want to allow starting projects with having nothing but a global cds-dk installed in a watched context.
2
+ // In that case, we want to fall back to resolving internal packages from the global cds-dk's own node_modules.
3
+ // Enriching NODE_PATH can be used in CJS (require(...)), but not in ESM (import ... from and import(...)),
4
+ // which requires a custom loader instead.
5
+ // https://nodejs.org/api/esm.html#no-node_path
6
+ const { createRequire, registerHooks } = require('node:module')
7
+ const { pathToFileURL } = require('node:url')
8
+
9
+ const ourModules = new Set(['@sap/cds'])
10
+
11
+ const requireFromHere = createRequire(__filename)
12
+ const resolveFromHere = url => pathToFileURL(requireFromHere.resolve(url)).href
13
+
14
+ registerHooks({
15
+ resolve(specifier, context, nextResolve) {
16
+ try {
17
+ // use default Node resolver first.
18
+ // Note: registering more hooks is UB!
19
+ return nextResolve(specifier, context)
20
+ } catch (error) {
21
+ if (error.code === 'ERR_MODULE_NOT_FOUND' && ourModules.has(specifier)) {
22
+ return { url: resolveFromHere(specifier), shortCircuit: true }
23
+ }
24
+ throw error
25
+ }
26
+ }
27
+ })
@@ -79,7 +79,7 @@ cds.on('bootstrap', app => {
79
79
 
80
80
  for (const viteApp of viteApps) {
81
81
  const base = viteApp.root ? '/' : normalizeBase(viteApp.name)
82
- const server = await createServer({ base, root: viteApp.path, server: { middlewareMode: true, hmr: { port: hmrPort++ } }})
82
+ const server = await createServer({ base, root: viteApp.path, server: { middlewareMode: true, ws: { port: hmrPort++ } }})
83
83
  viteServers.push({ base, root: viteApp.root, middlewares: server.middlewares })
84
84
  }
85
85
 
@@ -4,6 +4,15 @@
4
4
  - The format is based on [Keep a Changelog](https://keepachangelog.com/).
5
5
  - This project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## Version 10.0.4 - 2026-07-21
8
+
9
+ ### Fixed
10
+
11
+ - Default language fallbacks for incomplete `i18n` bundles in Java projects
12
+ - `srv.on/before('READ',...)` handlers registered with a cds-typer singular entity proxy were erroneously rewritten to event `each`
13
+ - Asynchronous plugins (exporting a Promise) were not awaited in ESM projects
14
+ - Fixed context propagation for local app and remote service calls
15
+
7
16
  ## Version 10.0.3 - 2026-07-02
8
17
 
9
18
  ### Fixed
@@ -133,6 +133,7 @@ module.exports = exports = {
133
133
  ],
134
134
  /** @deprecated */ fallback_bundle: '',
135
135
  /** @deprecated */ fatjson: false, // REVISIT: remove in cds^11
136
+ '[java]': { fatjson: true },
136
137
  },
137
138
 
138
139
  odata: {
@@ -36,9 +36,11 @@ exports.activate = async function (plugins = cds.env?.plugins || exports.fetch()
36
36
  }
37
37
  await Promise.all (all.map (([n,c]) => n in prioritized && _load(n,c)))
38
38
  await Promise.all (all.map (([n,c]) => n in prioritized || _load(n,c)))
39
- function _load (plugin, conf) {
39
+ async function _load (plugin, conf) {
40
40
  DEBUG?.log(`[cds.plugins] - loading ${plugin}:`, { impl: local(conf.impl) })
41
- return cds.utils._import (conf.impl)
41
+ const module = await cds.utils._import (conf.impl)
42
+ if (module?.default) await module.default
43
+ return module
42
44
  }
43
45
  DEBUG?.timeEnd ('[cds.plugins] - loaded plugins in')
44
46
  return plugins
@@ -48,7 +48,7 @@ class EventHandlers {
48
48
  return srv
49
49
  }
50
50
  else if (typeof path === 'object') {
51
- if (path.is_singular && event === 'READ') event = 'each' //> srv.after ('READ', Book, ...) w/ Book is a singular def from cds-typer
51
+ if (phase === 'after' && path.is_singular && event === 'READ') event = 'each' //> srv.after ('READ', Book, ...) w/ Book is a singular def from cds-typer
52
52
  path = path.name || cds.error.expected `${{path}} to be a string or an entity's CSN definition`
53
53
  }
54
54
  else if (typeof path === 'string') {
@@ -66,7 +66,7 @@ class ApplicationService extends cds.Service {
66
66
  // we need to provide target explicitly because it's cached within ensure_target
67
67
  const target = cds.infer.target(query, this)
68
68
  const _req = new cds.Request({ query, target, _resolved: true })
69
- return this.handle(_req).then(result => postProcess(_req, result, this))
69
+ return this.dispatch(_req).then(result => postProcess(_req, result, this))
70
70
  }
71
71
 
72
72
  // async
@@ -317,7 +317,7 @@ class RemoteService extends cds.Service {
317
317
  // we need to provide target explicitly because it's cached within ensure_target
318
318
  const target = cds.infer.target(query, this)
319
319
  const _req = new cds.Request({ query, target, _resolved: true, headers: req.headers, method: req.method })
320
- return this.handle(_req).then(result => postProcess(_req, result, this, true))
320
+ return this.dispatch(_req).then(result => postProcess(_req, result, this, true))
321
321
  }
322
322
 
323
323
  // we need to post process if alias was explicitly set in query
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds",
3
- "version": "10.0.3",
3
+ "version": "10.0.4",
4
4
  "description": "SAP Cloud Application Programming Model - CDS for Node.js",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [