@sap/cds-dk 9.5.0 → 9.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/bin/env.js +3 -3
- package/bin/up.js +22 -16
- package/bin/util/containerize.js +27 -14
- 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 +5 -0
- package/lib/init/projectReader.js +19 -8
- 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/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 +1 -0
- 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/npm-shrinkwrap.json +101 -102
- package/package.json +1 -1
- 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,35 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
8
8
|
|
|
9
|
+
## Version 9.6.0 - 2025-12-16
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `cds add console` adds dependency to the CAP console plugin.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- `cds import` no longer generates a first parameter typed as `$self` to bound actions, as this parameter is implicit.
|
|
18
|
+
- `cds add` sets `relativePaths: true` for generated UI5 apps to avoid deployment issues if UI5 data sources start with a "/".
|
|
19
|
+
- `cds up` automatically detects the preferred platform.
|
|
20
|
+
- Added deprecation notices for the following commands in favor of using `cds add kyma`:
|
|
21
|
+
- `cds add containerize`
|
|
22
|
+
- `cds add helm`
|
|
23
|
+
- `cds add helm-unified-runtime`
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- `cds build --ws-pack` now checks folder depth of up to 5.
|
|
28
|
+
- `cds import --config param1=true,param2=0` now writes boolean and number values correctly to `package.json`.
|
|
29
|
+
- `cds add sample` sets the correct UI component in _\_appconfig/fioriSandboxConfig.json_.
|
|
30
|
+
- `cds add xsuaa` correctly adds the authentication binding to the application deployer if executed afterwards.
|
|
31
|
+
- `cds env` prints coloured output by default again.
|
|
32
|
+
- `cds add java` will preserve existing _package.json_ settings.
|
|
33
|
+
- `cds add multitenancy` now correctly adds a `devDependencies` section when missing.
|
|
34
|
+
- `cds add mta` adds fixes an issue where a faulty XSUAA instance was added with `messaging: true` configured.
|
|
35
|
+
- `cds add sqlite --for production` correctly activates SQLite for production, not just development.
|
|
36
|
+
- `cds up -2 k8s` correctly runs `before_all` before the checksum calculation to calculate against the correct artifact.
|
|
37
|
+
|
|
9
38
|
## Version 9.5.0 - 2025-12-01
|
|
10
39
|
|
|
11
40
|
### Added
|
|
@@ -372,6 +401,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
|
372
401
|
- `cds add ams` for Java adds a custom builder to the `mta.yaml` to circumvent the missing `srv/src/gen/policies`.
|
|
373
402
|
- `cds add multitenancy` requires the `srv-api` instead of the `mtx-api` for Java projects.
|
|
374
403
|
|
|
404
|
+
|
|
405
|
+
## Version 8.9.12 - 2025-12-16
|
|
406
|
+
|
|
407
|
+
### Fixed
|
|
408
|
+
|
|
409
|
+
- `cds import` generates the on condition for a composition when the corresponding OData navigation property targets a collection, is contained (`$ContainsTarget` in OData EDM) and has a backlink (`$Partner` in OData EDM).
|
|
410
|
+
- `cds import` now considers OData contained navigation properties as compositions instead of associations.
|
|
411
|
+
- Support for `hdb` v2.
|
|
412
|
+
|
|
375
413
|
## Version 8.9.11 - 2025-11-21
|
|
376
414
|
|
|
377
415
|
### Fixed
|
package/bin/env.js
CHANGED
|
@@ -67,12 +67,12 @@ async function env ([cmd, key, value], options={}) {
|
|
|
67
67
|
env['_home_cds-dk'] = path.resolve(__dirname, '..') // to help tools find the DK
|
|
68
68
|
|
|
69
69
|
if (!options.for && options.mocked) await cds.service.bindings
|
|
70
|
+
options.colors = cds.utils.colors.enabled === true
|
|
70
71
|
return fn (env, key, value, options)
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
|
|
74
75
|
const { inspect, format } = require('node:util')
|
|
75
|
-
const {colors} = require('../lib/util/term')
|
|
76
76
|
const commands = {
|
|
77
77
|
|
|
78
78
|
json: (env,k,v,o) => commands.get (env,k,v,{...o,json:true}),
|
|
@@ -88,7 +88,7 @@ const commands = {
|
|
|
88
88
|
},
|
|
89
89
|
|
|
90
90
|
get (conf, key, val, options) {
|
|
91
|
-
let fmt = x => inspect (x, {depth:22, colors})
|
|
91
|
+
let fmt = x => inspect (x, { depth:22, colors: true })
|
|
92
92
|
|
|
93
93
|
let obj = (
|
|
94
94
|
!key ? conf
|
|
@@ -97,7 +97,7 @@ const commands = {
|
|
|
97
97
|
: conf.get(key.replace(/^cds\./,''))
|
|
98
98
|
)
|
|
99
99
|
|
|
100
|
-
if (
|
|
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
|
}
|
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
|
@@ -230,6 +230,11 @@ module.exports = class CDSGenerator {
|
|
|
230
230
|
async _fillTemplateList(plugins) {
|
|
231
231
|
const templates = new Map
|
|
232
232
|
for (let plugin of plugins) {
|
|
233
|
+
// cds 10 remove
|
|
234
|
+
if(plugin === "helm" || plugin === "helm-unified-runtime") {
|
|
235
|
+
cds.cli.options.add?.add("kyma")
|
|
236
|
+
cds.cli.argv[0] += ",kyma"
|
|
237
|
+
}
|
|
233
238
|
// Compat
|
|
234
239
|
if (plugin === 'helm' && cds.cli.options['internal-unified-runtime-charts']) {
|
|
235
240
|
console.warn(term.warn('"--internal-unified-runtime-charts" is deprecated, please use "cds add kyma --unified-runtime" instead'))
|
|
@@ -125,14 +125,17 @@ module.exports = new class ProjectReader {
|
|
|
125
125
|
const vizId = firstInbound.semanticObject + '-' + firstInbound.action
|
|
126
126
|
// Try to read archiveName from ui5.yaml if it exists
|
|
127
127
|
let archiveName = ''
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
128
|
+
const ui5YamlPaths = [
|
|
129
|
+
path.resolve(cds.root, cds.env.folders.app, app, 'ui5-deploy.yaml'),
|
|
130
|
+
path.resolve(cds.root, cds.env.folders.app, app, 'ui5.yaml')
|
|
131
|
+
]
|
|
132
|
+
for (const yamlPath of ui5YamlPaths) {
|
|
133
|
+
if (exists(yamlPath)) {
|
|
134
|
+
const ui5Deploy = cds.load.yaml(yamlPath)
|
|
135
|
+
const zipperTask = ui5Deploy?.builder?.customTasks?.find?.(t => t.name === 'ui5-task-zipper')
|
|
134
136
|
if (zipperTask?.configuration?.archiveName) {
|
|
135
137
|
archiveName = `${zipperTask.configuration.archiveName}.zip`
|
|
138
|
+
break
|
|
136
139
|
}
|
|
137
140
|
}
|
|
138
141
|
}
|
|
@@ -190,7 +193,14 @@ module.exports = new class ProjectReader {
|
|
|
190
193
|
values: values ?? {},
|
|
191
194
|
containerize: containerize ?? {}
|
|
192
195
|
}
|
|
193
|
-
}
|
|
196
|
+
},
|
|
197
|
+
hasHelmUnifiedRuntime: () =>
|
|
198
|
+
exists(join(cds.root, 'chart', 'Chart.yaml')) &&
|
|
199
|
+
(() => {
|
|
200
|
+
const chartYaml = cds.load.yaml(join(cds.root, 'chart', 'Chart.yaml'))
|
|
201
|
+
return chartYaml.annotations?.['app.kubernetes.io/managed-by'] === 'cds-dk/helm'
|
|
202
|
+
&& chartYaml.dependencies?.find(dep => dep.name === 'web-application')?.repository !== undefined
|
|
203
|
+
})()
|
|
194
204
|
}
|
|
195
205
|
|
|
196
206
|
// Automatically creates availability checks for `cds add` commands.
|
|
@@ -302,6 +312,7 @@ function _normalizeSpringBootCfg(obj) {
|
|
|
302
312
|
* @property {boolean} hasTyper Indicates if code supports CDS typer.
|
|
303
313
|
* @property {boolean} hasHelmUnifiedRuntime Indicates if the project uses Unified Runtime Helm deployment.
|
|
304
314
|
* @property {boolean} hasHelm Indicates if the project uses CAP Helm deployment.
|
|
315
|
+
* @property {boolean} hasKyma Indicates if the project uses Kyma.
|
|
305
316
|
* @property {boolean} hasMta Indicates if the project uses MTA deployment.
|
|
306
317
|
* @property {boolean} hasConnectivity Indicates if the project uses connectivity.
|
|
307
318
|
* @property {boolean} hasMultitenancy Indicates if the project is multitenant.
|
|
@@ -326,4 +337,4 @@ function _normalizeSpringBootCfg(obj) {
|
|
|
326
337
|
* @property {String} githubSlug GitHub org/repo.
|
|
327
338
|
* @property {boolean} isMonorepoMicroservice Indicates if the project is a monorepo microservice.
|
|
328
339
|
* @property {String} npmWorkspaceRoot The root path of the monorepo workspace.
|
|
329
|
-
*/
|
|
340
|
+
*/
|
|
@@ -28,7 +28,7 @@ module.exports = class AmsTemplate extends require('../../plugin') {
|
|
|
28
28
|
|
|
29
29
|
async combine() {
|
|
30
30
|
const project = readProject()
|
|
31
|
-
const { addMta, addCfManifest,
|
|
31
|
+
const { addMta, addCfManifest, addContainerize, addKyma } = project
|
|
32
32
|
|
|
33
33
|
if (addMta) {
|
|
34
34
|
await this.#combineWithMta(project)
|
|
@@ -38,7 +38,7 @@ module.exports = class AmsTemplate extends require('../../plugin') {
|
|
|
38
38
|
await this.#combineWithCfManifest(project)
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
if (
|
|
41
|
+
if (addKyma) {
|
|
42
42
|
await this.#combineWithHelm(project)
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -79,14 +79,14 @@ module.exports = class extends require('../../plugin') {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
// When Helm support is added
|
|
82
|
-
// if (hasContainerize) {
|
|
82
|
+
// if (hasContainerize || hasKyma) {
|
|
83
83
|
// const additions = [{
|
|
84
84
|
// in: 'modules',
|
|
85
85
|
// where: { name: `${project.appName}-content-deployer` }
|
|
86
86
|
// }]
|
|
87
87
|
// await merge(__dirname, '/files/containerize.yaml.hbs').into('containerize.yaml', { with: project, additions })
|
|
88
88
|
// }
|
|
89
|
-
// if (
|
|
89
|
+
// if (hasKyma) {
|
|
90
90
|
// await renderAndCopy(join(__dirname, 'files/ui-resources'), 'ui-resources', project)
|
|
91
91
|
// await merge(__dirname, 'files/values.yaml.hbs').into('chart/values.yaml', { with: project })
|
|
92
92
|
// }
|
|
@@ -14,8 +14,8 @@ module.exports = class ApplicationLoggingTemplate extends require('../../plugin'
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
async canRun() {
|
|
17
|
-
const { hasMta,
|
|
18
|
-
if (
|
|
17
|
+
const { hasMta, hasKyma } = readProject()
|
|
18
|
+
if (hasKyma && !hasMta) throw `'cds add application-logging' is not available for Kyma yet`
|
|
19
19
|
return true
|
|
20
20
|
}
|
|
21
21
|
|