@sap/cds-dk 9.4.1 → 9.4.3
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 +35 -0
- package/bin/data-products/index.js +10 -2
- package/bin/data-products/prepare-csn.js +63 -26
- package/bin/help.js +2 -2
- package/bin/import.js +7 -2
- package/bin/up.js +25 -4
- package/bin/util/containerize.js +142 -0
- package/lib/build/buildTaskEngine.js +5 -2
- package/lib/build/plugin.js +1 -1
- package/lib/build/plugins/helm/index.js +6 -7
- package/lib/build/provider/hana/index.js +10 -2
- package/lib/import/common.js +3 -3
- package/lib/import/message.js +1 -1
- package/lib/init/constants.js +3 -1
- package/lib/init/index.js +4 -5
- package/lib/init/projectReader.js +101 -77
- package/lib/init/registries/mta.js +3 -0
- package/lib/init/registries/xs-app.js +7 -1
- package/lib/init/template/ams/index.js +7 -6
- package/lib/init/template/app-frontend/files/mta.yaml.hbs +0 -7
- package/lib/init/template/app-frontend/index.js +2 -2
- package/lib/init/template/application-logging/index.js +13 -10
- package/lib/init/template/approuter/files/mta.yaml.hbs +7 -0
- package/lib/init/template/approuter/index.js +30 -18
- package/lib/init/template/attachments/index.js +3 -3
- package/lib/init/template/audit-logging/index.js +4 -4
- package/lib/init/template/cloud-logging/index.js +13 -10
- package/lib/init/template/connectivity/index.js +3 -3
- package/lib/init/template/containerize/files/containerize.yaml.hbs +1 -2
- package/lib/init/template/destination/index.js +5 -9
- package/lib/init/template/dynatrace/index.js +13 -10
- package/lib/init/template/enterprise-messaging/index.js +5 -5
- package/lib/init/template/enterprise-messaging-shared/index.js +4 -4
- package/lib/init/template/extensibility/index.js +2 -2
- package/lib/init/template/file-based-messaging/index.js +2 -2
- package/lib/init/template/github-actions/files/kyma-setup/action.yaml +0 -2
- package/lib/init/template/github-actions/index.js +3 -3
- package/lib/init/template/hana/index.js +19 -22
- package/lib/init/template/helm/index.js +47 -50
- package/lib/init/template/html5-repo/files/containerize.yaml.hbs +0 -10
- package/lib/init/template/html5-repo/files/mta.yaml.hbs +3 -7
- package/lib/init/template/html5-repo/files/values.yaml.hbs +6 -0
- package/lib/init/template/html5-repo/files/xs-app.json.hbs +5 -3
- package/lib/init/template/html5-repo/index.js +10 -8
- package/lib/init/template/http/index.js +31 -10
- package/lib/init/template/ias/files/mta.yaml.hbs +3 -0
- package/lib/init/template/ias/files/values.yaml.hbs +4 -1
- package/lib/init/template/ias/index.js +10 -11
- package/lib/init/template/kafka/index.js +3 -3
- package/lib/init/template/local-messaging/index.js +2 -2
- package/lib/init/template/malware-scanner/index.js +3 -7
- package/lib/init/template/mta/files/mta.yaml.hbs +10 -0
- package/lib/init/template/multitenancy/index.js +8 -10
- package/lib/init/template/notifications/index.js +2 -2
- package/lib/init/template/pipeline/index.js +5 -1
- package/lib/init/template/portal/files/mta.yaml.hbs +6 -0
- package/lib/init/template/portal/index.js +7 -7
- package/lib/init/template/postgres/index.js +4 -4
- package/lib/init/template/redis/index.js +2 -2
- package/lib/init/template/redis-messaging/index.js +2 -2
- package/lib/init/template/sample/files/app/admin-books/fiori-service.cds +2 -2
- package/lib/init/template/sample/files/javascript/admin-service.js.hbs +24 -0
- package/lib/init/template/sample/files/javascript/cat-service.js.hbs +43 -0
- package/lib/init/template/sample/files/typescript/admin-service.ts.hbs +16 -0
- package/lib/init/template/sample/files/typescript/cat-service.ts.hbs +31 -0
- package/lib/init/template/test/files/test/test.xs.hbs +1 -1
- package/lib/init/template/test/index.js +2 -2
- package/lib/init/template/tiny-sample/index.js +1 -1
- package/lib/init/template/workzone-standard/index.js +5 -6
- package/lib/init/template/xsuaa/files/mta.yaml.hbs +0 -2
- package/lib/init/template/xsuaa/index.js +24 -19
- package/lib/lint/eslintHelp.json +1 -1
- package/lib/util/question.js +1 -1
- package/npm-shrinkwrap.json +22 -22
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,41 @@ 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.4.3 - 2025-10-29
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- `cds add containerize` omits the Unix-based `before_all` scripts in `containerize.yaml` in favor of a cross-platform compatible `cds up --to k8s` integration.
|
|
14
|
+
- `cds up --to k8s` now uses built-in containerization, eliminating the hard dependency on the `ctz` library.
|
|
15
|
+
- `cds up --to k8s` will interactively ask for a domain if it can't be determined from the current Kubernetes configuration.
|
|
16
|
+
- `cds build --for hana` no longer requires `.hdbgrants` and `.hdbrevokes` to be specified as a plugin in the `.hdiconfig` file.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- `cds add pipeline` does not add UI5 template files in some cases.
|
|
21
|
+
- `cds add ias` does not write to the `mta.yaml` for non-MTA projects.
|
|
22
|
+
- `cds add ias` automatically adds the `authenticationType` to `xs-app.json` if required.
|
|
23
|
+
- `cds add ias` adds a binding to the approuter component if required.
|
|
24
|
+
- `cds import --from ...` yields a better error message for unknown import kinds.
|
|
25
|
+
|
|
26
|
+
## Version 9.4.2 - 2025-10-23
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- `cds add containerize` does not add app module installations scripts to its `containerize.yaml`. Use `cds up --to k8s` instead.
|
|
31
|
+
- `cds add` is less eager with respect to changing unaffected configuration.
|
|
32
|
+
- `cds add ias` adds less unnecessary configuration in combination with the HTML5 repository.
|
|
33
|
+
- `cds add test` includes the service name in the test suite description.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- `cds add ams` followed by `cds add mta` correctly generates the AMS deployer module.
|
|
38
|
+
- `cds add hana` does not fail if there's no `.cdsrc.json`.
|
|
39
|
+
- `cds up` doesn't try to create a symlink to the monorepo _package-lock.json_ if a submodule-local one already exists.
|
|
40
|
+
- `cds add test` creates correct OData URLs with unqualified entity names in service paths.
|
|
41
|
+
- `cds add http` skips contained composition targets.
|
|
42
|
+
- `cds add sample` no longer comes with an error when creating a Book through Fiori.
|
|
43
|
+
|
|
9
44
|
## Version 9.4.1 - 2025-10-09
|
|
10
45
|
|
|
11
46
|
### Added
|
|
@@ -6,8 +6,16 @@ const path = require ('node:path')
|
|
|
6
6
|
|
|
7
7
|
exports.load_data_product = async function (sources, o) {
|
|
8
8
|
let service = /\/([^/]+)\//.exec(o.services)?.[1].replaceAll('\\.','.')
|
|
9
|
-
if (
|
|
10
|
-
|
|
9
|
+
if (service != 'all') {
|
|
10
|
+
// ORD ID has been provided as parameter "-s", valid example: "sap.s4com:apiResource:Product:v1"
|
|
11
|
+
if (!service.match(/^([^:]+):apiResource:([^:]+):(v[0-9]+)$/))
|
|
12
|
+
throw new Error(`"${service}" is not a valid output port ORD ID`)
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
// try to deduce ORD ID from file name, valid example: "sap-s4com-Product-v1.json"
|
|
16
|
+
const m = /^(.+)-([^-]+)-(v[0-9]+)\.(json|csn)$/ .exec (path.basename(String(sources)))
|
|
17
|
+
if (m?.length != 5) throw new Error(`Output port ORD ID cannot be deduced from file name`)
|
|
18
|
+
const [,namespace,name,version] = m
|
|
11
19
|
service = namespace.replace(/-/g,'.') + ':apiResource:' + name + ':' + version
|
|
12
20
|
}
|
|
13
21
|
|
|
@@ -180,38 +180,75 @@ exports.prepareCsn = function (csn, srvName, ordId) {
|
|
|
180
180
|
//
|
|
181
181
|
// ---------- step 5: wrap everyting into service if not yet done -------------------------------
|
|
182
182
|
//
|
|
183
|
-
//
|
|
184
|
-
|
|
183
|
+
// This function must be called with a service name "srvName" as argument.
|
|
184
|
+
if (!srvName) throw new Error('No service name has been provided.')
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
186
|
+
// Assumptions (based on the rules for DP CSNs):
|
|
187
|
+
// 1) there is no or one context or service in the CSN
|
|
188
|
+
// 2) if there is a context or service, all objects in the CSN belong to it
|
|
189
|
+
//
|
|
190
|
+
// If there is no context/service (this is a DP CSN coming from BAH):
|
|
191
|
+
// put all entities into a new service "srvName", i.e.
|
|
192
|
+
// - prefix all object and association target names with "srvName"
|
|
193
|
+
// - add a service "srvName" with CAP specific annotations for DP handling
|
|
194
|
+
// If there is a context/service (DP CSN comes from UCL, context/service name is the schema name in the HDLFS share):
|
|
195
|
+
// move all entities out of the context/service and put them into a new service "srvName", i.e.
|
|
196
|
+
// - remove the context/service
|
|
197
|
+
// - change the prefix for all entity and association target names from
|
|
198
|
+
// the old context/service name to "srvName"
|
|
199
|
+
// - add a service "srvName" with CAP specific annotations for DP handling
|
|
200
|
+
//
|
|
201
|
+
// Note: Currently an existing context/service doesn't contain further info (e.g. annos),
|
|
202
|
+
// so deleting it is ok.
|
|
203
|
+
|
|
204
|
+
let ctxName = null;
|
|
205
|
+
const ctxs = Object.entries(csn.definitions).filter(([,o]) => o.kind === 'context' || o.kind === 'service')
|
|
206
|
+
if (ctxs.length > 0) {
|
|
207
|
+
// check assumption 1
|
|
208
|
+
if (ctxs.length > 1) throw new Error('Data Product CSN is invalid: it has more than one context or service.')
|
|
209
|
+
ctxName = ctxs[0][0]
|
|
210
|
+
delete csn.definitions[ctxName]
|
|
211
|
+
// check assumption 2
|
|
212
|
+
const bad = Object.keys(csn.definitions).filter((n) => !n.startsWith(ctxName + '.'))
|
|
213
|
+
if (bad.length > 0)
|
|
214
|
+
throw new Error(`Unexpected entity name "${bad[0]}" without prefix in Data product CSN with context "${ctxName}"`)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function changePrefix(name) {
|
|
218
|
+
if (ctxName) {
|
|
219
|
+
name = name.substring(ctxName.length + 1) // remove prefix incl. '.'
|
|
220
|
+
}
|
|
221
|
+
return srvName + '.' + name
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// loop over all definitions and prefix names with service name
|
|
225
|
+
// if ctxName accidentially equals srvName, do nothing
|
|
226
|
+
if (ctxName != srvName) {
|
|
227
|
+
for (let n in csn.definitions) {
|
|
228
|
+
let def = csn.definitions[n]
|
|
229
|
+
// adapt assoc targets
|
|
230
|
+
for (let en in def.elements || {} ) {
|
|
231
|
+
let element = def.elements[en]
|
|
232
|
+
if (element?.type === 'cds.Association' || element?.type === 'cds.Composition') {
|
|
233
|
+
element.target = changePrefix(element.target)
|
|
203
234
|
}
|
|
204
235
|
}
|
|
236
|
+
csn.definitions[changePrefix(n)] = def
|
|
237
|
+
delete csn.definitions[n]
|
|
205
238
|
}
|
|
206
|
-
// add service annotations
|
|
207
|
-
Object.assign(csn.definitions[srvName], {
|
|
208
|
-
'@cds.dp.ordId': ordId,
|
|
209
|
-
'@cds.external': true,
|
|
210
|
-
'@data.product': true,
|
|
211
|
-
'@protocol' : 'none' // don't serve via OData
|
|
212
|
-
})
|
|
213
239
|
}
|
|
214
240
|
|
|
241
|
+
// add the service
|
|
242
|
+
csn.definitions[srvName] = {
|
|
243
|
+
kind : 'service',
|
|
244
|
+
'@cds.dp.ordId': ordId,
|
|
245
|
+
'@cds.external': true,
|
|
246
|
+
'@data.product': true,
|
|
247
|
+
'@protocol' : 'none' // don't serve via OData
|
|
248
|
+
};
|
|
249
|
+
if (ctxName) csn.definitions[srvName]['@data.product.schema'] = ctxName;
|
|
250
|
+
|
|
251
|
+
|
|
215
252
|
//
|
|
216
253
|
// ---------- step 6: move entity/element annotations into extensions section -------------------
|
|
217
254
|
//
|
package/bin/help.js
CHANGED
|
@@ -32,10 +32,10 @@ module.exports = exports = Object.assign (help, {formatHelp, handleCompletion, h
|
|
|
32
32
|
* * | *logout* logout from extensible multitenant SaaS app
|
|
33
33
|
* * | *pull* pull base model of extensible SaaS app
|
|
34
34
|
* * | *push* push extension to extensible SaaS app
|
|
35
|
-
*
|
|
35
|
+
* * | *lint* run linter for env or model checks
|
|
36
36
|
*v* | *version* get detailed version information
|
|
37
37
|
* * | *completion* add/remove cli completion for cds commands
|
|
38
|
-
|
|
38
|
+
*h* | *help* get detailed usage information
|
|
39
39
|
|
|
40
40
|
Learn more about each command using:
|
|
41
41
|
*cds help* <command> or
|
package/bin/import.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module.exports = Object.assign(_import, {
|
|
2
2
|
_name: 'import',
|
|
3
|
-
options: [ '--services', '--for', '--out', '--as', '--name', '--include-namespaces', '--dependencies', '--from', '--config', '--destination', '--group' ],
|
|
3
|
+
options: [ '--services', '--for', '--out', '--as', '--name', '--include-namespaces', '--dependencies', '--from', '--config', '--destination', '--group', '--data-product' ],
|
|
4
4
|
shortcuts: [ '-s', '-4', '-o', '-as', '-n', '-ns', '-d', undefined, '-c', undefined, undefined, '-', '-f'],
|
|
5
5
|
flags: [ '--dry', '--force', '--no-copy', '--no-save', '--keep-namespace', '--data-product', '--edmx', '--rfc', '--odata', '--openapi', '--asyncapi' ],
|
|
6
6
|
help: `
|
|
@@ -55,6 +55,11 @@ module.exports = Object.assign(_import, {
|
|
|
55
55
|
Destination name under which the remote service can be reached.
|
|
56
56
|
Not supported by import types "edmx", "openapi" or "asyncapi".
|
|
57
57
|
|
|
58
|
+
*--data-product*
|
|
59
|
+
Tells cds import to import a data product. The source must be a valid
|
|
60
|
+
data product CSN file, e.g. as found on http://api.sap.com/dataproducts.
|
|
61
|
+
(Not released with general availability yet)
|
|
62
|
+
|
|
58
63
|
*Below options are valid for OData only*
|
|
59
64
|
|
|
60
65
|
*--keep-namespace*
|
|
@@ -153,7 +158,7 @@ async function _import(args, options = {}) {
|
|
|
153
158
|
if (options.from) {
|
|
154
159
|
if (!cds.import.from[options.from]) {
|
|
155
160
|
// the required plugin might not be installed, e.g. rfc importer
|
|
156
|
-
throw messages.UNSUPPORTED_FROM_OPTION;
|
|
161
|
+
throw messages.UNSUPPORTED_FROM_OPTION.replace('{0}', options.from).replace('{1}', Object.keys(cds.import.from).join(', '));
|
|
157
162
|
}
|
|
158
163
|
csn = await cds.import.from[options.from](input, apiOptions);
|
|
159
164
|
} else {
|
package/bin/up.js
CHANGED
|
@@ -32,6 +32,8 @@ const cds = require('../lib/cds')
|
|
|
32
32
|
const { highlight } = require('../lib/util/term')
|
|
33
33
|
const { exists, fs, path, path: { join } } = cds.utils
|
|
34
34
|
const { readProject } = require('../lib/init/projectReader')
|
|
35
|
+
const { mkdirp, copy } = cds.utils
|
|
36
|
+
const { containerize } = require('../bin/util/containerize')
|
|
35
37
|
|
|
36
38
|
const run = cmd => {
|
|
37
39
|
console.log(highlight(cmd))
|
|
@@ -49,10 +51,16 @@ async function up() {
|
|
|
49
51
|
const project = readProject()
|
|
50
52
|
const { apps, appName, hasApprouter, hasXsuaa, isMonorepoMicroservice } = project
|
|
51
53
|
|
|
54
|
+
const html5DeployerPath = join(cds.root, cds.env.folders.app, 'html5-deployer', 'resources')
|
|
55
|
+
|
|
52
56
|
for (const { app } of apps) {
|
|
53
57
|
const appPath = path.join(cds.root, cds.env.folders.app, app)
|
|
54
|
-
if (exists(path.join(appPath, 'package.json'))
|
|
55
|
-
|
|
58
|
+
if (exists(path.join(appPath, 'package.json'))) {
|
|
59
|
+
if (!exists(path.join(appPath, 'package-lock.json'))) {
|
|
60
|
+
run(`npm i --prefix ${appPath}`)
|
|
61
|
+
} else if (process.env.CI) {
|
|
62
|
+
run(`npm ci --prefix ${appPath}`)
|
|
63
|
+
}
|
|
56
64
|
}
|
|
57
65
|
}
|
|
58
66
|
|
|
@@ -75,6 +83,16 @@ async function up() {
|
|
|
75
83
|
|
|
76
84
|
if (cds.cli.options.to === 'k8s') {
|
|
77
85
|
if (!exists('chart')) run('cds add helm')
|
|
86
|
+
if (apps.length > 0) {
|
|
87
|
+
await mkdirp(html5DeployerPath)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const { app } of apps) {
|
|
91
|
+
const appPath = path.join(cds.root, cds.env.folders.app, app)
|
|
92
|
+
run(`npm run build --prefix ${appPath}`)
|
|
93
|
+
await copy(path.join(appPath, `dist/${app}.zip`)).to(html5DeployerPath, `${app}.zip`)
|
|
94
|
+
}
|
|
95
|
+
|
|
78
96
|
if (!exists('containerize.yaml')) run('cds add containerize')
|
|
79
97
|
} else {
|
|
80
98
|
if (!exists('mta.yaml')) {
|
|
@@ -91,7 +109,7 @@ async function up() {
|
|
|
91
109
|
const target = path.join(process.cwd(), 'package-lock.json')
|
|
92
110
|
if (exists(target) && fs.lstatSync(target).isSymbolicLink()) fs.unlinkSync(target)
|
|
93
111
|
const relative = path.relative(process.cwd(), source)
|
|
94
|
-
fs.symlinkSync(relative, target, 'junction')
|
|
112
|
+
if (!exists(target)) fs.symlinkSync(relative, target, 'junction')
|
|
95
113
|
}
|
|
96
114
|
|
|
97
115
|
if (cds.cli.options.to === 'k8s') {
|
|
@@ -100,7 +118,10 @@ async function up() {
|
|
|
100
118
|
const nsKubectl = ns ? `-n ${ns}` : ''
|
|
101
119
|
|
|
102
120
|
run('npm i --package-lock')
|
|
103
|
-
run('
|
|
121
|
+
run('cds build --production')
|
|
122
|
+
await containerize('containerize.yaml', { push: true, clearCache: !!process.env.CLEAR_CACHE })
|
|
123
|
+
console.log('Containerization completed')
|
|
124
|
+
|
|
104
125
|
let helmUpgrade = `helm upgrade --install ${appName} ./gen/chart ${nsHelm} --wait --wait-for-jobs --timeout=10m`
|
|
105
126
|
if (hasXsuaa) helmUpgrade += ' --set-file xsuaa.jsonParameters=xs-security.json'
|
|
106
127
|
run(helmUpgrade)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
const cds = require('../../lib/cds')
|
|
2
|
+
const { path } = cds.utils
|
|
3
|
+
const { join } = path
|
|
4
|
+
const { execSync } = require('node:child_process')
|
|
5
|
+
const { highlight } = require('../../lib/util/term')
|
|
6
|
+
|
|
7
|
+
const buildpacks = new Set([
|
|
8
|
+
'java', 'nodejs', 'sap-machine', 'executable-jar', 'spring-boot', 'syft'
|
|
9
|
+
])
|
|
10
|
+
const builders = new Set([
|
|
11
|
+
'builder-jammy-base', 'builder-jammy-buildpackless-base', 'builder-jammy-full', 'builder-jammy-buildpackless-full'
|
|
12
|
+
])
|
|
13
|
+
|
|
14
|
+
const keyProcessorMap = {
|
|
15
|
+
dockerfile: processDockerfile,
|
|
16
|
+
commands: processCommands,
|
|
17
|
+
buildpack: processBuildpack
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const run = cmd => {
|
|
21
|
+
cmd = Array.isArray(cmd) ? cmd.join(' ') : cmd
|
|
22
|
+
console.log(highlight(cmd))
|
|
23
|
+
try {
|
|
24
|
+
return execSync(cmd, {
|
|
25
|
+
stdio: 'inherit',
|
|
26
|
+
env: { ...process.env, FORCE_COLOR: cds.utils.colors.enabled }
|
|
27
|
+
})
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw error.message
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function appendSubcommand(param, subcommand) {
|
|
34
|
+
if (!param) return []
|
|
35
|
+
if (subcommand === '--env') {
|
|
36
|
+
return Object.entries(param).flatMap(([k, v]) =>
|
|
37
|
+
String(v).split(',').map(env => [subcommand, `${k}=${env.trim() || '""'}`])
|
|
38
|
+
).flat()
|
|
39
|
+
}
|
|
40
|
+
return param.split(',').map(e => [subcommand, e.trim()]).flat()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function processDockerfile(bp, name, tag) {
|
|
44
|
+
if (!bp.dockerfile) return []
|
|
45
|
+
return [['docker', 'build',
|
|
46
|
+
...appendSubcommand(`${name}:${tag ?? 'latest'}`, '-t'),
|
|
47
|
+
...appendSubcommand(bp.dockerfile, '-f'),
|
|
48
|
+
'.']]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function processCommands(bp) {
|
|
52
|
+
return bp.commands ? bp.commands.map(c => c.trim().split(' ')) : []
|
|
53
|
+
}
|
|
54
|
+
function processBuildpack(bp, name, tag, clearCache = false) {
|
|
55
|
+
let { type, path, env, builder } = bp.buildpack
|
|
56
|
+
type = type ? type.split(',').map(t => buildpacks.has(t.trim()) ? `paketo-buildpacks/${t.trim()}` : t.trim()) : []
|
|
57
|
+
if (builders.has(builder)) builder = `paketobuildpacks/${builder}`
|
|
58
|
+
const cacheArgs = clearCache ? ['--clear-cache'] : []
|
|
59
|
+
return [['pack',
|
|
60
|
+
...appendSubcommand(`${name}:${tag}`, 'build'),
|
|
61
|
+
...appendSubcommand(path, '--path'),
|
|
62
|
+
...type.flatMap(t => appendSubcommand(t, '--buildpack')),
|
|
63
|
+
...appendSubcommand(builder, '--builder'),
|
|
64
|
+
...appendSubcommand(env, '--env'),
|
|
65
|
+
...cacheArgs
|
|
66
|
+
]]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Parses a YAML configuration file to extract Docker build and push commands for modules.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} filename - The path to the YAML file to parse.
|
|
73
|
+
* @param {string} [repoOpt=''] - Optional repository override. If not provided, uses the repository defined in the YAML file.
|
|
74
|
+
* @param {boolean} [clearCache=false] - Whether to clear cache for buildpack builds.
|
|
75
|
+
* @returns {{
|
|
76
|
+
* commands: Array<{
|
|
77
|
+
* name: string,
|
|
78
|
+
* tag: string,
|
|
79
|
+
* buildCmd: Array<string>,
|
|
80
|
+
* tagCmd: Array<string>,
|
|
81
|
+
* pushCmd: Array<string>
|
|
82
|
+
* }>,
|
|
83
|
+
* before_all: Array<Array<string>>,
|
|
84
|
+
* repository: string
|
|
85
|
+
* }} An object containing the commands for each module, any global pre-commands, and the repository name.
|
|
86
|
+
* @throws {Error} If modules are not defined in the YAML file or if build-parameters are invalid.
|
|
87
|
+
*/
|
|
88
|
+
function parseYAML(filename, repoOpt = '', options) {
|
|
89
|
+
const file = cds.load.yaml(join(cds.root, filename))
|
|
90
|
+
const { repository: repoFile, modules, tag: globalTag = 'latest' } = file
|
|
91
|
+
const repository = repoOpt || repoFile
|
|
92
|
+
const before_all = file['before-all'] ? file['before-all'].map(c => c.trim().split(' ')) : []
|
|
93
|
+
if (!modules) throw new Error('Modules not defined')
|
|
94
|
+
console.log(`Using repository: ${repository}`)
|
|
95
|
+
const commands = modules.map(m => {
|
|
96
|
+
const name = m.name, tag = m.tag || globalTag, bp = m['build-parameters']
|
|
97
|
+
const source = `${name}:${tag}`, repoImg = `${repository}/${source}`
|
|
98
|
+
const key = Object.keys(bp)[0]
|
|
99
|
+
const buildType = keyProcessorMap[key]
|
|
100
|
+
if (!buildType) throw new Error(`build-parameters ${Object.keys(bp)} is invalid.`)
|
|
101
|
+
return {
|
|
102
|
+
name, tag,
|
|
103
|
+
buildCmd: key === 'buildpack'
|
|
104
|
+
? buildType(bp, name, tag, options.clearCache)
|
|
105
|
+
: buildType(bp, name, tag),
|
|
106
|
+
tagCmd: ['docker', 'tag', source, repoImg],
|
|
107
|
+
pushCmd: ['docker', 'push', repoImg]
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
return { commands, before_all, repository }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Executes the containerization commands for each module.
|
|
115
|
+
* @param {*} modules - The list of modules with their respective build, tag, and push commands.
|
|
116
|
+
* @param {*} options - Options object, e.g., { push: true } to push images after building.
|
|
117
|
+
*/
|
|
118
|
+
async function executeModules(modules, options) {
|
|
119
|
+
for (const m of modules) {
|
|
120
|
+
console.log(`Containerising module ${m.name}`)
|
|
121
|
+
for (const cmd of m.buildCmd) run(cmd)
|
|
122
|
+
if (options.push) {
|
|
123
|
+
run(m.tagCmd)
|
|
124
|
+
run(m.pushCmd)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Main function to containerize modules based on a YAML configuration file.
|
|
131
|
+
* @param {string} filename - The path to the YAML configuration file.
|
|
132
|
+
* @param {{ push: boolean, clearCache: boolean }} options - Options object, e.g., { push: true } to push images after building.
|
|
133
|
+
* @returns {Promise<void>}
|
|
134
|
+
*/
|
|
135
|
+
async function containerize(filename, options = { push: false, clearCache: false }) {
|
|
136
|
+
const { repository, before_all, commands: modules } = parseYAML(filename, '', options)
|
|
137
|
+
if (!repository || !modules) return
|
|
138
|
+
for (const cmd of before_all) await run(cmd)
|
|
139
|
+
await executeModules(modules, options)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = { containerize }
|
|
@@ -179,7 +179,7 @@ class BuildTaskEngine {
|
|
|
179
179
|
|
|
180
180
|
/** @private */
|
|
181
181
|
_resolveHandlerResponse(results, pluginMessages = []) {
|
|
182
|
-
/** @type {BuildError[]}*/
|
|
182
|
+
/** @type {(BuildError | Error)[]}*/
|
|
183
183
|
const errors = []
|
|
184
184
|
const resolvedResults = results.reduce((acc, r) => {
|
|
185
185
|
if (r.status === 'fulfilled') {
|
|
@@ -195,7 +195,10 @@ class BuildTaskEngine {
|
|
|
195
195
|
const error = errors.find(e => e.constructor.name !== COMPILATION_ERROR)
|
|
196
196
|
if (error) {
|
|
197
197
|
// throw original BuildError including existing BuildMessages
|
|
198
|
-
|
|
198
|
+
if (!(error instanceof BuildError)) {
|
|
199
|
+
throw error
|
|
200
|
+
}
|
|
201
|
+
error.messages = error.messages?.map(m => typeof m === 'string' ? new BuildMessage(m, SEVERITY_ERROR) : m)
|
|
199
202
|
error.messages = this._sortMessagesUnique(pluginMessages.filter(m => m instanceof BuildMessage), error.messages)
|
|
200
203
|
throw error
|
|
201
204
|
}
|
package/lib/build/plugin.js
CHANGED
|
@@ -126,7 +126,7 @@ class Plugin {
|
|
|
126
126
|
* @abstract
|
|
127
127
|
* Called by the framework to create the artifacts of this build plugin.
|
|
128
128
|
*/
|
|
129
|
-
async build() { /* abstract */}
|
|
129
|
+
async build() { /* abstract */ }
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
132
|
* Called by the framework immediately before 'build' to delete any output created by this build plugin.
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const cds = require('../../../cds')
|
|
2
|
-
const yaml =
|
|
3
|
-
const { exists, path, readFileSync } = cds.utils
|
|
2
|
+
const { exists, path, readFileSync, yaml } = cds.utils
|
|
4
3
|
const { Plugin, BuildError } = require('../..')
|
|
5
4
|
const { join } = path
|
|
6
5
|
const { readProject } = require('../../../init/projectReader')
|
|
@@ -11,7 +10,7 @@ module.exports = class HelmBuildPlugin extends Plugin {
|
|
|
11
10
|
static hasTask() {
|
|
12
11
|
if (exists('chart/Chart.yaml')) {
|
|
13
12
|
const chartFile = readFileSync(join(cds.root, 'chart', 'Chart.yaml'), 'utf8')
|
|
14
|
-
const chartYaml = yaml.
|
|
13
|
+
const chartYaml = yaml.parse(chartFile)
|
|
15
14
|
return chartYaml.annotations?.["app.kubernetes.io/managed-by"] === 'cds-dk/helm'
|
|
16
15
|
}
|
|
17
16
|
return false
|
|
@@ -27,15 +26,16 @@ module.exports = class HelmBuildPlugin extends Plugin {
|
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
async build() {
|
|
30
|
-
if(!HelmBuildPlugin.hasTask()) {
|
|
29
|
+
if (!HelmBuildPlugin.hasTask()) {
|
|
31
30
|
this.pushMessage("Helm Chart not found. Execute `cds add helm` to add the helm chart.", Plugin.WARNING)
|
|
32
31
|
return
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
const
|
|
34
|
+
const project = readProject()
|
|
35
|
+
const { hasApprouter, hasConnectivity, hasHtml5Repo } = project
|
|
36
36
|
|
|
37
37
|
const chartFile = readFileSync(join(cds.root, 'chart', 'Chart.yaml'), 'utf8')
|
|
38
|
-
const chartYaml = yaml.
|
|
38
|
+
const chartYaml = yaml.parse(chartFile)
|
|
39
39
|
const isInternal = chartYaml.dependencies?.find(dep => dep.name === 'web-application')?.repository !== undefined
|
|
40
40
|
|
|
41
41
|
if (!isInternal) {
|
|
@@ -66,7 +66,6 @@ module.exports = class HelmBuildPlugin extends Plugin {
|
|
|
66
66
|
await this.copy(join(pluginResourcePath, 'connectivity/connectivity-proxy-info.yaml')).to('templates/connectivity-proxy-info.yaml')
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
70
69
|
if (hasHtml5Repo) {
|
|
71
70
|
await this.copy(join(pluginResourcePath, 'html5-repo/html5-apps-deployer-configmap.yaml')).to('templates/html5-apps-deployer-configmap.yaml')
|
|
72
71
|
}
|
|
@@ -21,6 +21,10 @@ const FILE_NAME_UNDEPLOY_JSON = "undeploy.json"
|
|
|
21
21
|
const DEPLOY_FORMAT = "deploy-format"
|
|
22
22
|
const DEBUG = cds.debug('cli|build')
|
|
23
23
|
|
|
24
|
+
// Some HDI artifacts do not require explicit plugin specification in _.hdiconfig_.
|
|
25
|
+
// This list contains exceptions which will be ignored during the consistency check.
|
|
26
|
+
const PLUGINLESS_HDB_FILES = new Set([".hdbgrants", ".hdbrevokes"])
|
|
27
|
+
|
|
24
28
|
class HanaBuildPlugin extends InternalBuildPlugin {
|
|
25
29
|
init() {
|
|
26
30
|
if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
|
|
@@ -473,8 +477,12 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
473
477
|
}
|
|
474
478
|
}
|
|
475
479
|
if (Object.keys(content['file_suffixes']).length < hdiPlugins.size) {
|
|
476
|
-
const missingPlugins = Array.from(hdiPlugins)
|
|
477
|
-
|
|
480
|
+
const missingPlugins = Array.from(hdiPlugins)
|
|
481
|
+
.filter(key => !content['file_suffixes'][key.slice(1)])
|
|
482
|
+
.filter(key => !PLUGINLESS_HDB_FILES.has(key))
|
|
483
|
+
if (missingPlugins.length) {
|
|
484
|
+
this.pushMessage(`'HANA database plugin not found for following file suffix [${missingPlugins.join(', ')}]`)
|
|
485
|
+
}
|
|
478
486
|
}
|
|
479
487
|
await this.write(content).to(hdiConfigPath)
|
|
480
488
|
}
|
package/lib/import/common.js
CHANGED
|
@@ -336,12 +336,12 @@ function _printUsageHint(dest, service, cwd) {
|
|
|
336
336
|
dest = dest.replace(extension, '');
|
|
337
337
|
let using
|
|
338
338
|
if (service.length > 1){
|
|
339
|
-
using = `using { ${service.join(', ')}} from './${path.relative(cds.env.folders.srv, dest).replace(/\\/g, '/')}'
|
|
339
|
+
using = `using { ${service.join(', ')}} from './${path.relative(cds.env.folders.srv, dest).replace(/\\/g, '/')}';`;
|
|
340
340
|
} else {
|
|
341
|
-
using = `using { ${service} as external } from './${path.relative(cds.env.folders.srv, dest).replace(/\\/g, '/')}'
|
|
341
|
+
using = `using { ${service} as external } from './${path.relative(cds.env.folders.srv, dest).replace(/\\/g, '/')}';`;
|
|
342
342
|
}
|
|
343
343
|
const message = `\n[cds] - imported API to ${path.relative(cwd, dest)}
|
|
344
|
-
> use it in your CDS models through the
|
|
344
|
+
> use it in your CDS models through the likes of:
|
|
345
345
|
|
|
346
346
|
${require('../util/term').info(using)}
|
|
347
347
|
`;
|
package/lib/import/message.js
CHANGED
|
@@ -12,7 +12,7 @@ const messages = {
|
|
|
12
12
|
FILE_MODIFIED: 'The file with the same name exists and is been modified. Use --force flag to forcefully overwrite the file.',
|
|
13
13
|
OUTPUT_FILE_MISMATCH: 'Specify the file extension that --as option supports.',
|
|
14
14
|
INVALID_AS_OPTION: 'For --as option, please specify one of these output file format [cds, csn, json].',
|
|
15
|
-
UNSUPPORTED_FROM_OPTION:
|
|
15
|
+
UNSUPPORTED_FROM_OPTION: `No importer is installed for type '{0}'. Known import kinds are: {1}. Make sure the required importer plugin is installed.`,
|
|
16
16
|
INCORRECT_EXTENSION: 'If --as option is not provided, then please provide either .json or .csn output file extension.',
|
|
17
17
|
INCORRECT_INPUT_NAMESPACES: 'Invalid value for --include-namespaces option. Specify namespaces as comma-separated list or "*"',
|
|
18
18
|
MISSING_ASSOCIATION: 'Association is missing in the input file provided. Please check the edmx file.',
|
package/lib/init/constants.js
CHANGED
|
@@ -5,7 +5,7 @@ module.exports = {
|
|
|
5
5
|
COMMAND_ADD: 'add',
|
|
6
6
|
|
|
7
7
|
JAVA_LTS_VERSIONS: [17, 21],
|
|
8
|
-
MAVEN_ARCHETYPE_VERSION: '4.4.
|
|
8
|
+
MAVEN_ARCHETYPE_VERSION: '4.4.1',
|
|
9
9
|
|
|
10
10
|
OPTIONS: Object.freeze({
|
|
11
11
|
COMPLETION: 'completion',
|
|
@@ -57,6 +57,8 @@ module.exports = {
|
|
|
57
57
|
APP_FRONTEND: 'app-frontend',
|
|
58
58
|
PORTAL: 'portal',
|
|
59
59
|
APPLICATION_LOGGING: 'application-logging',
|
|
60
|
+
CLOUD_LOGGING: 'cloud-logging',
|
|
61
|
+
TELEMETRY: 'telemetry',
|
|
60
62
|
AUDIT_LOGGING: 'audit-logging',
|
|
61
63
|
NOTIFICATIONS: 'notifications',
|
|
62
64
|
WORKZONE: 'workzone',
|
package/lib/init/index.js
CHANGED
|
@@ -161,9 +161,7 @@ module.exports = class CDSGenerator {
|
|
|
161
161
|
if (!cds.cli.options.dry) console.log(`adding ${term.bold(facet)}`)
|
|
162
162
|
const template = this.templateList[i]
|
|
163
163
|
await template.run()
|
|
164
|
-
await template.combine()
|
|
165
164
|
}
|
|
166
|
-
|
|
167
165
|
DEBUG && console.log()
|
|
168
166
|
DEBUG && console.log('plugins active for production:')
|
|
169
167
|
const fromDk = readdirSync(join(__dirname, './template'))
|
|
@@ -176,7 +174,7 @@ module.exports = class CDSGenerator {
|
|
|
176
174
|
for (const plugin of sorted) {
|
|
177
175
|
const Plugin = registered.get(plugin) ?? require('./template/' + plugin)
|
|
178
176
|
const template = new Plugin()
|
|
179
|
-
const inProduction = Plugin.hasInProduction(env4('production')) || cds.cli.argv
|
|
177
|
+
const inProduction = Plugin.hasInProduction(env4('production')) || cds.cli.argv[0]?.includes(plugin) || cds.cli.options.add?.has(plugin)
|
|
180
178
|
DEBUG && console.log(inProduction ? '✅' : '❌', plugin)
|
|
181
179
|
if (inProduction) await template.combine()
|
|
182
180
|
}
|
|
@@ -236,8 +234,8 @@ module.exports = class CDSGenerator {
|
|
|
236
234
|
if (plugin === 'appfront') plugin = 'app-front'
|
|
237
235
|
if (plugin === 'helm' && cds.cli.options['internal-unified-runtime-charts']) {
|
|
238
236
|
plugin = 'helm-unified-runtime'
|
|
239
|
-
cds.cli.options.add
|
|
240
|
-
cds.cli.options.add
|
|
237
|
+
cds.cli.options.add?.delete('helm')
|
|
238
|
+
cds.cli.options.add?.add('helm-unified-runtime')
|
|
241
239
|
}
|
|
242
240
|
if (plugin === 'sample-tiny') plugin = 'tiny-sample'
|
|
243
241
|
if (plugin in { 'kibana-logging': 1, 'kibana': 1 }) plugin = 'application-logging'
|
|
@@ -250,6 +248,7 @@ module.exports = class CDSGenerator {
|
|
|
250
248
|
if (await template.canRun()) {
|
|
251
249
|
templates.set(plugin, template)
|
|
252
250
|
const dependencies = await template.requires()
|
|
251
|
+
dependencies?.forEach(d => cds.cli.options.add?.add(d))
|
|
253
252
|
dependencies?.forEach(d => plugins.add(d))
|
|
254
253
|
} else {
|
|
255
254
|
throw 'cannot run plugin \'' + plugin + '\'' + (DEBUG ? 'Does not satisfy the canRun() function:\n ' + template.canRun : '')
|