@sap/cds-dk 9.5.0 → 9.6.1
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 +52 -0
- package/bin/cds.js +1 -1
- package/bin/compile/index.js +3 -2
- package/bin/env.js +3 -3
- package/bin/up.js +22 -16
- package/bin/util/containerize.js +27 -14
- package/lib/build/provider/internalBuildPlugin.js +23 -0
- package/lib/build/util.js +33 -16
- package/lib/import/common.js +7 -1
- package/lib/import/odata/v4parser.js +25 -38
- package/lib/init/constants.js +1 -1
- package/lib/init/index.js +7 -1
- package/lib/init/projectReader.js +21 -10
- package/lib/init/template/ams/index.js +2 -2
- package/lib/init/template/app-frontend/files/ui5.yaml.hbs +1 -0
- package/lib/init/template/app-frontend/index.js +2 -2
- package/lib/init/template/application-logging/index.js +2 -2
- package/lib/init/template/approuter/index.js +9 -5
- package/lib/init/template/attachments/index.js +4 -2
- package/lib/init/template/audit-logging/index.js +4 -2
- package/lib/init/template/cloud-logging/index.js +4 -2
- package/lib/init/template/connectivity/index.js +2 -2
- package/lib/init/template/console/files/package.json +5 -0
- package/lib/init/template/console/index.js +25 -0
- package/lib/init/template/containerize/index.js +4 -2
- package/lib/init/template/destination/index.js +4 -2
- package/lib/init/template/dynatrace/files/package.json.hbs +2 -2
- package/lib/init/template/dynatrace/index.js +4 -2
- package/lib/init/template/enterprise-messaging/files/mta.yaml.hbs +3 -1
- package/lib/init/template/enterprise-messaging/index.js +4 -2
- package/lib/init/template/enterprise-messaging-shared/index.js +4 -2
- package/lib/init/template/github-actions/files/workflows/release.yaml.hbs +2 -2
- package/lib/init/template/github-actions/index.js +4 -4
- package/lib/init/template/hana/index.js +4 -4
- package/lib/init/template/helm/index.js +6 -3
- package/lib/init/template/helm-unified-runtime/index.js +1 -0
- package/lib/init/template/html5-repo/files/ui5.yaml.hbs +1 -0
- package/lib/init/template/html5-repo/index.js +9 -7
- package/lib/init/template/ias/index.js +2 -2
- package/lib/init/template/java/index.js +14 -0
- package/lib/init/template/kafka/index.js +4 -2
- package/lib/init/template/kyma/index.js +124 -13
- package/lib/init/template/malware-scanner/index.js +4 -2
- package/lib/init/template/mta/index.js +1 -1
- package/lib/init/template/multitenancy/index.js +7 -4
- package/lib/init/template/nodejs/files/.vscode/extensions.json +0 -3
- package/lib/init/template/pipeline/files/ui5.yaml.hbs +1 -0
- package/lib/init/template/portal/index.js +6 -3
- package/lib/init/template/postgres/index.js +2 -2
- package/lib/init/template/redis/index.js +2 -3
- package/lib/init/template/redis-messaging/index.js +2 -3
- package/lib/init/template/sample/files/_i18n/i18n_de.properties +9 -0
- package/lib/init/template/sample/files/_i18n/i18n_fr.properties +1 -0
- package/lib/init/template/sample/files/app/_i18n/i18n.properties +17 -0
- package/lib/init/template/sample/files/app/_i18n/i18n_de.properties +16 -2
- package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n.properties +3 -11
- package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n_de.properties +3 -0
- package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n_en.properties +3 -0
- package/lib/init/template/sample/files/app/admin-authors/webapp/manifest.json +1 -1
- package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n.properties +3 -11
- package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n_de.properties +3 -0
- package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n_en.properties +3 -0
- package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +1 -1
- package/lib/init/template/sample/files/app/appconfig/fioriSandboxConfig.json +6 -6
- package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n.properties +3 -11
- package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n_de.properties +3 -0
- package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n_en.properties +3 -0
- package/lib/init/template/sample/files/app/browse/webapp/manifest.json +1 -1
- package/lib/init/template/sample/files/app/common.cds +2 -1
- package/lib/init/template/sample/files/app/fiori-apps.html +32 -12
- package/lib/init/template/sample/files/app/genres/fiori-service.cds +1 -1
- package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n.properties +3 -4
- package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n_de.properties +3 -2
- package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n_en.properties +3 -0
- package/lib/init/template/sample/files/app/genres/webapp/manifest.json +2 -2
- package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Genres.texts.csv +43 -0
- package/lib/init/template/sample/index.js +14 -1
- package/lib/init/template/sqlite/files/package.json.hbs +13 -0
- package/lib/init/template/sqlite/index.js +1 -1
- package/lib/init/template/xsuaa/index.js +5 -2
- package/lib/lint/eslintHelp.json +1 -1
- package/lib/util/checksum.js +19 -10
- package/lib/util/fs.js +8 -1
- package/lib/util/glob.js +26 -13
- package/lib/util/term.js +3 -1
- package/lib/watch/livereload-server.js +4 -3
- package/lib/watch/node-watch.js +5 -8
- package/lib/watch/watcher.js +136 -0
- package/npm-shrinkwrap.json +110 -117
- package/package.json +2 -3
- package/lib/init/template/sample/files/app/_i18n/i18n_en.properties +0 -8
- package/lib/init/template/sqlite/files/package.json +0 -5
- /package/lib/init/template/{helm → kyma}/files/Chart.yaml.hbs +0 -0
- /package/lib/init/template/{containerize → kyma}/files/containerize.yaml.hbs +0 -0
- /package/lib/init/template/{helm → kyma}/files/values.schema.json +0 -0
- /package/lib/init/template/{helm → kyma}/files/values.yaml.hbs +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,49 @@ 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.1 - 2026-01-09
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- All commands use colored logs in GitHub Actions workflows by default.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- CVE-2025-15284: update `express` to fix vulnerability with `qs` <6.14.1.
|
|
18
|
+
- `cds watch` is more efficient on Linux with respect to `inotify` events. This fixes some crash and stall situations on Linux, especially in SAP Business Application Studio where resources are shared.
|
|
19
|
+
- `cds build --production` correctly includes `message.properties` from i18n files provided by CAP plugins.
|
|
20
|
+
- `cds add sample` fixes a warning about unused imports in _app/common.cds_.
|
|
21
|
+
- `cds init` fixes some rare argument parsing errors.
|
|
22
|
+
|
|
23
|
+
## Version 9.6.0 - 2025-12-16
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- `cds add console` adds dependency to the CAP console plugin.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- `cds import` no longer generates a first parameter typed as `$self` to bound actions, as this parameter is implicit.
|
|
32
|
+
- `cds add` sets `relativePaths: true` for generated UI5 apps to avoid deployment issues if UI5 data sources start with a "/".
|
|
33
|
+
- `cds up` automatically detects the preferred platform.
|
|
34
|
+
- Added deprecation notices for the following commands in favor of using `cds add kyma`:
|
|
35
|
+
- `cds add containerize`
|
|
36
|
+
- `cds add helm`
|
|
37
|
+
- `cds add helm-unified-runtime`
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- `cds build --ws-pack` now checks folder depth of up to 5.
|
|
42
|
+
- `cds import --config param1=true,param2=0` now writes boolean and number values correctly to `package.json`.
|
|
43
|
+
- `cds add sample` sets the correct UI component in _\_appconfig/fioriSandboxConfig.json_.
|
|
44
|
+
- `cds add xsuaa` correctly adds the authentication binding to the application deployer if executed afterwards.
|
|
45
|
+
- `cds env` prints coloured output by default again.
|
|
46
|
+
- `cds add java` will preserve existing _package.json_ settings.
|
|
47
|
+
- `cds add multitenancy` now correctly adds a `devDependencies` section when missing.
|
|
48
|
+
- `cds add mta` adds fixes an issue where a faulty XSUAA instance was added with `messaging: true` configured.
|
|
49
|
+
- `cds add sqlite --for production` correctly activates SQLite for production, not just development.
|
|
50
|
+
- `cds up -2 k8s` correctly runs `before_all` before the checksum calculation to calculate against the correct artifact.
|
|
51
|
+
|
|
9
52
|
## Version 9.5.0 - 2025-12-01
|
|
10
53
|
|
|
11
54
|
### Added
|
|
@@ -372,6 +415,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
|
372
415
|
- `cds add ams` for Java adds a custom builder to the `mta.yaml` to circumvent the missing `srv/src/gen/policies`.
|
|
373
416
|
- `cds add multitenancy` requires the `srv-api` instead of the `mtx-api` for Java projects.
|
|
374
417
|
|
|
418
|
+
|
|
419
|
+
## Version 8.9.12 - 2025-12-16
|
|
420
|
+
|
|
421
|
+
### Fixed
|
|
422
|
+
|
|
423
|
+
- `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).
|
|
424
|
+
- `cds import` now considers OData contained navigation properties as compositions instead of associations.
|
|
425
|
+
- Support for `hdb` v2.
|
|
426
|
+
|
|
375
427
|
## Version 8.9.11 - 2025-11-21
|
|
376
428
|
|
|
377
429
|
### Fixed
|
package/bin/cds.js
CHANGED
package/bin/compile/index.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
const path = require('path')
|
|
2
|
-
const {colors} = require('../../lib/util/term')
|
|
3
1
|
|
|
4
2
|
module.exports = Object.assign ( compile, {
|
|
5
3
|
handleCompletion,
|
|
@@ -188,6 +186,8 @@ module.exports = Object.assign ( compile, {
|
|
|
188
186
|
|
|
189
187
|
`})
|
|
190
188
|
|
|
189
|
+
const path = require('path')
|
|
190
|
+
|
|
191
191
|
async function handleCompletion(currentWord, previousWord, argv, util) {
|
|
192
192
|
const allOptionsFlags = [
|
|
193
193
|
...compile.options ?? [],
|
|
@@ -389,6 +389,7 @@ async function compile (models, options={}) {
|
|
|
389
389
|
function consoleLog(o) {
|
|
390
390
|
if (typeof o === 'string') return console.log (o)
|
|
391
391
|
if (process.stdout.isTTY) {
|
|
392
|
+
const { enabled: colors } = require('../../lib').utils.colors
|
|
392
393
|
o = inspect(o,{colors, depth: 111, compact:false}).replace(/\[Object: null prototype\] /g, '')
|
|
393
394
|
} else
|
|
394
395
|
o = JSON.stringify(o, null, 2)
|
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 (
|
|
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.
|
|
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 (
|
|
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/${
|
|
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(
|
|
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 (
|
|
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
|
|
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 =
|
|
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'
|
|
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
|
|
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
|
}
|
package/bin/util/containerize.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
|
@@ -152,10 +152,33 @@ class InternalBuildPlugin extends Plugin {
|
|
|
152
152
|
// bundleDest might be null
|
|
153
153
|
if (bundleDest && Object.keys(bundles).length > 0) {
|
|
154
154
|
await this.write(bundles).to(file)
|
|
155
|
+
// also collect and write messages from plugins
|
|
156
|
+
await this.collectMessageBundles(bundleDest)
|
|
155
157
|
return { file, bundles }
|
|
156
158
|
}
|
|
157
159
|
return null
|
|
158
160
|
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Collect and write message bundles from CAP plugins into messages.properties files.
|
|
164
|
+
* @param {string} bundleDest
|
|
165
|
+
*/
|
|
166
|
+
async collectMessageBundles(bundleDest) {
|
|
167
|
+
const messageBundle = cds.i18n.bundle4('messages')
|
|
168
|
+
const messagesData = messageBundle.translations4?.(cds.env.i18n.languages) ?? {}
|
|
169
|
+
|
|
170
|
+
// Write messages.properties files for each locale
|
|
171
|
+
for (const [locale, messages] of Object.entries(messagesData)) {
|
|
172
|
+
if (Object.keys(messages).length > 0) {
|
|
173
|
+
const fileName = locale ? `messages_${locale}.properties` : 'messages.properties'
|
|
174
|
+
const filePath = path.join(bundleDest, fileName)
|
|
175
|
+
const content = Object.entries(messages)
|
|
176
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
177
|
+
.join('\n') + '\n'
|
|
178
|
+
await this.write(content).to(filePath)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
159
182
|
}
|
|
160
183
|
|
|
161
184
|
// Emits the 'compile.for.runtime' event, as the produced CSN is for runtime usage.
|
package/lib/build/util.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
const
|
|
3
|
-
const
|
|
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
|
|
6
|
-
const
|
|
7
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
296
|
-
|
|
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
|
}
|
package/lib/import/common.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
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
|
|
1689
|
-
if (entity.
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
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[
|
|
1697
|
-
const backLink = Object.keys(compTarget.elements).find(e =>
|
|
1698
|
-
compTarget.elements[e].type === 'cds.Association' &&
|
|
1699
|
-
compTarget.elements[e].target ===
|
|
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]
|
|
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) {
|
package/lib/init/constants.js
CHANGED
package/lib/init/index.js
CHANGED
|
@@ -174,7 +174,8 @@ module.exports = class CDSGenerator {
|
|
|
174
174
|
for (const plugin of sorted) {
|
|
175
175
|
const Plugin = registered.get(plugin) ?? require('./template/' + plugin)
|
|
176
176
|
const template = new Plugin()
|
|
177
|
-
const
|
|
177
|
+
const added = cds.cli.command === 'add' && cds.cli.argv[0]?.split(',').includes(plugin) || cds.cli.options.add?.has(plugin)
|
|
178
|
+
const inProduction = Plugin.hasInProduction(env4('production')) || added
|
|
178
179
|
DEBUG && console.log(inProduction ? '✅' : '❌', plugin)
|
|
179
180
|
if (inProduction) await template.combine()
|
|
180
181
|
}
|
|
@@ -230,6 +231,11 @@ module.exports = class CDSGenerator {
|
|
|
230
231
|
async _fillTemplateList(plugins) {
|
|
231
232
|
const templates = new Map
|
|
232
233
|
for (let plugin of plugins) {
|
|
234
|
+
// cds 10 remove
|
|
235
|
+
if(plugin === "helm" || plugin === "helm-unified-runtime") {
|
|
236
|
+
cds.cli.options.add?.add("kyma")
|
|
237
|
+
cds.cli.argv[0] += ",kyma"
|
|
238
|
+
}
|
|
233
239
|
// Compat
|
|
234
240
|
if (plugin === 'helm' && cds.cli.options['internal-unified-runtime-charts']) {
|
|
235
241
|
console.warn(term.warn('"--internal-unified-runtime-charts" is deprecated, please use "cds add kyma --unified-runtime" instead'))
|