@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.
- package/CHANGELOG.md +37 -0
- package/bin/debug.js +6 -2
- package/bin/export.js +1 -1
- package/bin/up.js +1 -1
- package/bin/version.js +7 -3
- package/bin/watch.js +1 -0
- package/lib/build/plugins/hana/index.js +19 -2
- package/lib/build/plugins/helm/files/templates/_deployment_helpers.tpl +2 -0
- package/lib/build/plugins/nodejs/index.js +6 -3
- package/lib/build/util.js +1 -1
- package/lib/init/projectReader.js +4 -2
- package/lib/init/template/ams/files/containerize.yaml.hbs +1 -0
- package/lib/init/template/ams/files/package.json.hbs +1 -1
- package/lib/init/template/approuter/files/values.yaml.hbs +9 -1
- package/lib/init/template/data/as-csv.js +4 -2
- package/lib/init/template/data/as-json.js +4 -1
- package/lib/init/template/github-actions/files/workflows/cf.yaml.hbs +6 -6
- package/lib/init/template/ias/files/values.yaml.hbs +4 -4
- package/lib/init/template/portal/files/values.yaml.hbs +12 -0
- package/lib/init/template/sample/files/app/browse/fiori-service.cds +2 -0
- package/lib/init/template/test/files/test/test.xs.hbs +3 -2
- package/lib/init/template/typer/files/xsconfig.json +1 -1
- package/lib/init/template/typescript/files/package.json +1 -1
- package/lib/init/template/xsuaa/index.js +11 -7
- package/lib/lint/eslintHelp.json +1 -1
- package/lib/watch/esm-resolve-registration.js +27 -0
- package/lib/watch/vite-watch.js +1 -1
- package/node_modules/@sap/cds/CHANGELOG.md +9 -0
- package/node_modules/@sap/cds/lib/env/defaults.js +1 -0
- package/node_modules/@sap/cds/lib/plugins.js +4 -2
- package/node_modules/@sap/cds/lib/srv/srv-handlers.js +1 -1
- package/node_modules/@sap/cds/libx/_runtime/common/Service.js +1 -1
- package/node_modules/@sap/cds/libx/_runtime/remote/Service.js +1 -1
- package/node_modules/@sap/cds/package.json +1 -1
- package/node_modules/@sap/cds-compiler/CHANGELOG.md +26 -0
- package/node_modules/@sap/cds-compiler/bin/cdsse.js +1 -1
- package/node_modules/@sap/cds-compiler/bin/cdsv2m.js +1 -1
- package/node_modules/@sap/cds-compiler/doc/IncompatibleChanges_v7.md +20 -13
- package/node_modules/@sap/cds-compiler/lib/base/builtins.js +4 -1
- package/node_modules/@sap/cds-compiler/lib/base/message-registry.js +15 -2
- package/node_modules/@sap/cds-compiler/lib/checks/defaultValues.js +0 -1
- package/node_modules/@sap/cds-compiler/lib/compiler/populate.js +1 -0
- package/node_modules/@sap/cds-compiler/lib/compiler/resolve.js +7 -6
- package/node_modules/@sap/cds-compiler/lib/compiler/tweak-assocs.js +27 -6
- package/node_modules/@sap/cds-compiler/lib/edm/annotations/edmJson.js +5 -0
- package/node_modules/@sap/cds-compiler/lib/json/from-csn.js +3 -3
- package/node_modules/@sap/cds-compiler/lib/json/to-csn.js +5 -4
- package/node_modules/@sap/cds-compiler/lib/model/csnUtils.js +13 -8
- package/node_modules/@sap/cds-compiler/lib/parsers/AstBuildingParser.js +14 -3
- package/node_modules/@sap/cds-compiler/lib/tool-lib/enrichCsn.js +1 -1
- package/node_modules/@sap/cds-compiler/lib/transform/db/cdsPersistence.js +3 -2
- package/node_modules/@sap/cds-compiler/lib/transform/db/views.js +2 -2
- package/node_modules/@sap/cds-compiler/lib/transform/transformUtils.js +1 -2
- package/node_modules/@sap/cds-compiler/package.json +1 -1
- package/node_modules/media-typer/README.md +15 -14
- package/node_modules/media-typer/index.js +0 -8
- package/node_modules/media-typer/package.json +16 -12
- package/node_modules/ws/lib/receiver.js +15 -32
- package/node_modules/ws/lib/websocket-server.js +4 -4
- package/node_modules/ws/lib/websocket.js +4 -4
- package/node_modules/ws/package.json +5 -1
- 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}
|
|
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} --
|
|
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)
|
|
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
|
|
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<
|
|
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', '
|
|
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
|
-
|
|
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
|
|
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
|
|
253
|
-
for (let yamlDoc of
|
|
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)
|
|
@@ -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(...
|
|
43
|
+
header.push(...fkHeaders)
|
|
42
44
|
}
|
|
43
|
-
record.push(...
|
|
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
|
-
|
|
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@
|
|
47
|
+
- uses: cap-js/cf-setup@v2
|
|
48
48
|
with:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
+
{}
|
|
31
|
+
// <%¶msString%>
|
|
31
32
|
}`
|
|
32
33
|
// TODO finish this test
|
|
33
34
|
// expect(data.value).to...
|
|
@@ -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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
}
|
package/lib/lint/eslintHelp.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "10.
|
|
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
|
+
})
|
package/lib/watch/vite-watch.js
CHANGED
|
@@ -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,
|
|
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
|
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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
|