@sap/cds-dk 9.4.2 → 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.
Files changed (36) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/bin/data-products/index.js +10 -2
  3. package/bin/data-products/prepare-csn.js +63 -26
  4. package/bin/help.js +2 -2
  5. package/bin/import.js +7 -2
  6. package/bin/up.js +24 -3
  7. package/bin/util/containerize.js +142 -0
  8. package/lib/build/buildTaskEngine.js +5 -2
  9. package/lib/build/plugin.js +1 -1
  10. package/lib/build/plugins/helm/index.js +6 -6
  11. package/lib/build/provider/hana/index.js +10 -2
  12. package/lib/import/common.js +3 -3
  13. package/lib/import/message.js +1 -1
  14. package/lib/init/projectReader.js +37 -22
  15. package/lib/init/registries/mta.js +3 -0
  16. package/lib/init/registries/xs-app.js +7 -1
  17. package/lib/init/template/approuter/files/mta.yaml.hbs +7 -0
  18. package/lib/init/template/approuter/index.js +17 -11
  19. package/lib/init/template/audit-logging/index.js +2 -2
  20. package/lib/init/template/containerize/files/containerize.yaml.hbs +1 -2
  21. package/lib/init/template/destination/index.js +0 -4
  22. package/lib/init/template/github-actions/files/kyma-setup/action.yaml +0 -2
  23. package/lib/init/template/hana/index.js +3 -3
  24. package/lib/init/template/helm/index.js +16 -28
  25. package/lib/init/template/html5-repo/files/containerize.yaml.hbs +0 -9
  26. package/lib/init/template/html5-repo/files/mta.yaml.hbs +0 -7
  27. package/lib/init/template/html5-repo/files/values.yaml.hbs +6 -0
  28. package/lib/init/template/html5-repo/files/xs-app.json.hbs +5 -3
  29. package/lib/init/template/html5-repo/index.js +2 -2
  30. package/lib/init/template/ias/index.js +4 -5
  31. package/lib/init/template/multitenancy/index.js +2 -2
  32. package/lib/init/template/pipeline/index.js +5 -1
  33. package/lib/init/template/xsuaa/index.js +3 -3
  34. package/lib/util/question.js +1 -1
  35. package/npm-shrinkwrap.json +17 -17
  36. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -6,6 +6,23 @@ 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
+
9
26
  ## Version 9.4.2 - 2025-10-23
10
27
 
11
28
  ### Changed
@@ -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 (!service.includes(':apiResource:')) {
10
- const [,namespace,name,version] = /^(.+)-([^-]+)-(v[0-9]+)\.json$/ .exec (path.basename(String(sources)))
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
- // Prefix all entity names with service name. Adapt association targets.
184
- // Add CAP specific annotations for DP handling.
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
- if (srvName) {
187
- if (!csn.definitions[srvName] || csn.definitions[srvName].kind !== 'service') {
188
- // add the service
189
- csn.definitions[srvName] = {kind: 'service'}
190
- // loop over all entities and prefix names with service name
191
- for (let n in csn.definitions) {
192
- let entity = csn.definitions[n]
193
- if (entity.kind === 'entity') {
194
- // find association elements and rename the targets
195
- for (let en in entity.elements || {} ) {
196
- let element = entity.elements[en]
197
- if (element?.type == 'cds.Association' || element?.type == 'cds.Composition') {
198
- element.target = srvName + '.' + element.target
199
- }
200
- }
201
- csn.definitions[srvName + '.' + n] = entity
202
- delete csn.definitions[n]
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
- *t* | *lint* run linter for env or model checks
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
- *?* | *help* get detailed usage information
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')) && !exists(path.join(appPath, 'package-lock.json'))) {
55
- run(`npm i --prefix ${appPath}`)
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')) {
@@ -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('ctz containerize.yaml --log --push')
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
- error.messages = error.messages.map(m => typeof m === 'string' ? new BuildMessage(m, SEVERITY_ERROR) : m)
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
  }
@@ -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 = require('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.parseDocument(chartFile).toJS()
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 { hasApprouter, hasConnectivity, hasHtml5Repo } = readProject()
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.parseDocument(chartFile).toJS()
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) {
@@ -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).filter(key => !content['file_suffixes'][key.slice(1)])
477
- this.pushMessage(`'HANA database plugin not found for following file suffix [${missingPlugins.join(', ')}]`)
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
  }
@@ -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 like of:
344
+ > use it in your CDS models through the likes of:
345
345
 
346
346
  ${require('../util/term').info(using)}
347
347
  `;
@@ -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: 'For --from option, please specify one of these types [edmx, openapi, asyncapi, rfc]. Make sure the required importer plugin is installed.',
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.',
@@ -15,7 +15,7 @@ module.exports = new class ProjectReader {
15
15
 
16
16
  #setPackageInfo() {
17
17
  this.isJava = (exists('pom.xml') || cds.cli.options?.add?.has('java'))
18
- && !cds.env.for('cds').profiles.includes('node') // opt-out for hybrid projects like sflight
18
+ && !cds.env.for('cds').profiles.includes('node') // opt-out for hybrid projects like sflight
19
19
 
20
20
  // 1. Use project name and static default values
21
21
  this.appVersion = this.isJava ? '1.0.0-SNAPSHOT' : '1.0.0'
@@ -32,7 +32,7 @@ module.exports = new class ProjectReader {
32
32
  const v = version?.[0] === '${revision}' ? properties?.[0]?.revision[0] : version?.[0] ?? '1.0.0-SNAPSHOT'
33
33
  if (v && REGEX_JAVA_VERSION.test(v)) this.appVersion = v
34
34
  this.jdkVersion = properties?.[0]?.['jdk.version']?.[0]?._text?.[0] ?? JAVA_LTS_VERSIONS[JAVA_LTS_VERSIONS.length - 1]
35
- if (artifactId?.[0]) this.appName = this.appId = artifactId?.[0]?.split(/-parent/)[0]
35
+ if (artifactId?.[0]) this.appName = this.appId = artifactId?.[0]?.split(/-parent/)[0]
36
36
  if (description?.[0]) this.appDescription = description[0]
37
37
  }
38
38
  else if (exists('package.json')) {
@@ -73,9 +73,9 @@ module.exports = new class ProjectReader {
73
73
  TRACE?.({ env })
74
74
 
75
75
  const pkgJson = () => exists('package.json') ? JSON.parse(fs.readFileSync(join(cds.root, 'package.json'))) : {}
76
- const _isESM = () => pkgJson().type === 'module'
76
+ const _isESM = () => pkgJson().type === 'module'
77
77
  const _isTypescript = () => !!pkgJson().dependencies?.typescript || !!pkgJson().devDependencies?.typescript
78
- const _hasTyper = () => !!pkgJson().dependencies?.['@cap-js/cds-typer'] || !!pkgJson().devDependencies?.['@cap-js/cds-typer']
78
+ const _hasTyper = () => !!pkgJson().dependencies?.['@cap-js/cds-typer'] || !!pkgJson().devDependencies?.['@cap-js/cds-typer']
79
79
  const _inProd = plugin => require(`./template/${plugin}`).hasInProduction(env) || cds.cli.argv[0]?.includes(plugin) || cds.cli.options.add?.has(plugin)
80
80
  const _uiEmbedded = () => exists(join(env.folders.app, 'xs-app.json'))
81
81
  const _uiModule = () => !_uiEmbedded() && isdir(env.folders.app) && fs.readdirSync(resolve(cds.root, env.folders.app)).length > 0
@@ -121,9 +121,22 @@ module.exports = new class ProjectReader {
121
121
  const manifestPath = path.resolve(cds.root, cds.env.folders.app, app, 'webapp/manifest.json')
122
122
  const manifest = exists(manifestPath) ? JSON.parse(fs.readFileSync(manifestPath, 'utf-8')) : {}
123
123
  const inbounds = manifest?.['sap.app']?.crossNavigation?.inbounds
124
- const [firstInbound] = Object.values(inbounds ?? { intent: { semanticObject: 'Books', action: 'display' }})
124
+ const [firstInbound] = Object.values(inbounds ?? { intent: { semanticObject: 'Books', action: 'display' } })
125
125
  const vizId = firstInbound.semanticObject + '-' + firstInbound.action
126
- return { app, strippedApp: app.replace(/-/g, ''), vizId, isNotLast: i < apps.length - 1, manifestAppId: manifest?.['sap.app']?.id ?? app}
126
+ // Try to read archiveName from ui5-deploy.yaml if it exists
127
+ let archiveName = ''
128
+ const ui5DeployPath = path.resolve(cds.root, cds.env.folders.app, app, 'ui5-deploy.yaml')
129
+ if (exists(ui5DeployPath)) {
130
+ const ui5Deploy = cds.load.yaml(ui5DeployPath)
131
+ const customTasks = ui5Deploy?.builder?.customTasks
132
+ if (Array.isArray(customTasks)) {
133
+ const zipperTask = customTasks.find(t => t.name === 'ui5-task-zipper')
134
+ if (zipperTask?.configuration?.archiveName) {
135
+ archiveName = `${zipperTask.configuration.archiveName}.zip`
136
+ }
137
+ }
138
+ }
139
+ return { app, strippedApp: app.replace(/-/g, ''), vizId, isNotLast: i < apps.length - 1, manifestAppId: manifest?.['sap.app']?.id ?? app, archiveName }
127
140
  })
128
141
  },
129
142
  appUIPaths: () => {
@@ -133,7 +146,7 @@ module.exports = new class ProjectReader {
133
146
  isdir(path.join(cds.root, cds.env.folders.app, e)) && isApp(e)
134
147
  )
135
148
  },
136
- hasIndexHtml: () => {},
149
+ hasIndexHtml: () => { },
137
150
  appPath: () => env.folders.app,
138
151
  appVersion: () => appVersion,
139
152
  jdkVersion: () => jdkVersion,
@@ -152,6 +165,7 @@ module.exports = new class ProjectReader {
152
165
  sap: () => cds.cli.options.sap,
153
166
  imageRegistry: () => exists('chart/values.yaml') ? cds.load.yaml(join(cds.root, 'chart/values.yaml')).global?.image?.registry ?? '<your-container-registry-server>' : '<your-container-registry-server>',
154
167
  extendsApp: _extends,
168
+ isBas: () => Object.entries(process.env).some(([key, value]) => key === 'WS_BASE_URL' && value.includes('applicationstudio') || /THEIA_(DEFAULT_)?PLUGINS/.test(key)),
155
169
  npmWorkspaceRoot: () => {
156
170
  if (this.cachedWorkspaceRoot) return this.cachedWorkspaceRoot
157
171
  this.cachedWorkspaceRoot = execSync('npm prefix', { encoding: 'utf8', cwd: cds.root, stdio: 'pipe' }).trim()
@@ -160,7 +174,7 @@ module.exports = new class ProjectReader {
160
174
  isMonorepoMicroservice: function () {
161
175
  return this.npmWorkspaceRoot() !== cds.root
162
176
  },
163
- hasRoles: () => {}, roles: () => {}, // for xs-security generation
177
+ hasRoles: () => { }, roles: () => { }, // for xs-security generation
164
178
  hasCustomRoleCollections: () => exists('xs-security.json') && !!JSON.parse(fs.readFileSync(path.join(cds.root, 'xs-security.json')))['role-collections']
165
179
  }
166
180
 
@@ -169,7 +183,8 @@ module.exports = new class ProjectReader {
169
183
  // E.g. `cds add hana` can be checked using `hasHana` or `isHana`
170
184
  const project = (() => {
171
185
  const defined = {}
172
- const _get = property => { const p = property
186
+ const _get = property => {
187
+ const p = property
173
188
  if (p in reserved) return reserved[p]()
174
189
  for (const prefix of ['has', 'is', 'add']) if (p.startsWith(prefix) && p.length > prefix.length) {
175
190
  const facet = p.slice(prefix.length).replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
@@ -200,23 +215,23 @@ module.exports = new class ProjectReader {
200
215
  * @param {string[]?} roots array of directories to search for application.yaml, like `srv`
201
216
  * @returns {Promise<object[]>} array of objects with `cds` property
202
217
  */
203
- module.exports._readSpringBootConfig = async function (roots=[cds.env.folders.srv]) {
218
+ module.exports._readSpringBootConfig = async function (roots = [cds.env.folders.srv]) {
204
219
  const configNodes = []
205
220
  const rootDirs = roots.map(d => join(cds.root, d))
206
221
  for (let r of rootDirs) {
207
- const file = isfile (join (r,'./src/main/resources/application.yaml'))
208
- if (file) {
209
- const yaml = cds.load.yaml(file)
210
- for (let yamlDoc of Array.isArray(yaml) ? yaml : [yaml]) {
211
- let cds = yamlDoc?.cds;
212
- if (!cds) continue
213
- cds = _normalizeSpringBootCfg(cds)
214
- configNodes.push({cds})
222
+ const file = isfile(join(r, './src/main/resources/application.yaml'))
223
+ if (file) {
224
+ const yaml = cds.load.yaml(file)
225
+ for (let yamlDoc of Array.isArray(yaml) ? yaml : [yaml]) {
226
+ let cds = yamlDoc?.cds;
227
+ if (!cds) continue
228
+ cds = _normalizeSpringBootCfg(cds)
229
+ configNodes.push({ cds })
230
+ }
215
231
  }
216
- }
217
232
  }
218
233
  return configNodes
219
- }
234
+ }
220
235
 
221
236
  /**
222
237
  * Normalize SpringBoot's dots in keys to express nested objects
@@ -226,7 +241,7 @@ module.exports._readSpringBootConfig = async function (roots=[cds.env.folders.sr
226
241
  * @returns {object}
227
242
  */
228
243
  function _normalizeSpringBootCfg(obj) {
229
- if (typeof obj !== 'object') return obj
244
+ if (typeof obj !== 'object') return obj
230
245
  Object.keys(obj).forEach(k => {
231
246
  const prop = k.split('.')
232
247
  const last = prop.pop()
@@ -239,7 +254,7 @@ function _normalizeSpringBootCfg(obj) {
239
254
  // recursively normalize
240
255
  _normalizeSpringBootCfg(obj[k])
241
256
  // delete the original property from object if it was rewritten
242
- if (prop.length) delete obj[k]
257
+ if (prop.length) delete obj[k]
243
258
  })
244
259
  return obj
245
260
  }
@@ -15,6 +15,8 @@ const appFront = {
15
15
  where: { type: 'org.cloudfoundry.managed-service', 'parameters.service': 'app-front' }
16
16
  }
17
17
 
18
+ const ias = { in: 'resources', where: { 'parameters.service': 'identity' } }
19
+
18
20
  const xsuaa = {
19
21
  in: 'resources',
20
22
  where: { type: 'org.cloudfoundry.managed-service', 'parameters.service': 'xsuaa', 'parameters.service-plan': 'application' }
@@ -208,6 +210,7 @@ const auditlog = {
208
210
  module.exports = {
209
211
  srv4,
210
212
  approuter,
213
+ ias,
211
214
  xsuaa,
212
215
  xsuaa4,
213
216
  destination,
@@ -5,6 +5,12 @@ const odataApi = {
5
5
  where: { target: '/odata/$1' }
6
6
  }
7
7
 
8
+ const html5RepoApi = {
9
+ in: 'routes',
10
+ where: { service: 'html5-apps-repo-rt' }
11
+ }
12
+
8
13
  module.exports = {
9
- odataApi
14
+ odataApi,
15
+ html5RepoApi
10
16
  }
@@ -83,3 +83,10 @@ resources:
83
83
  oauth2-configuration:
84
84
  redirect-uris:
85
85
  - https://*~{app-api/app-uri}/**
86
+
87
+ - name: {{appName}}-ias
88
+ type: org.cloudfoundry.managed-service
89
+ requires:
90
+ - name: app-api
91
+ parameters:
92
+ service: identity
@@ -5,7 +5,7 @@ const { readProject } = require('../../projectReader')
5
5
  const { sort, merge } = require('../../merge')
6
6
  const {
7
7
  srv4, mtxSidecar4, approuter, // Modules
8
- xsuaa, // BTP Services
8
+ xsuaa, ias, // BTP Services
9
9
  requiredMtxApi, requiredJavaApprouterApi4, providedJavaApprouterApi, // APIs
10
10
  requiredAppApi4, providedAppApi,
11
11
  approuterMTXRoute,
@@ -34,13 +34,13 @@ module.exports = class ApprouterTemplate extends require('../../plugin') {
34
34
  const project = readProject()
35
35
  const {
36
36
  isNodejs, isJava,
37
- addXsuaa, hasXsuaa, addMultitenancy, hasMTXRoute, hasMTXRouteJava,
37
+ addIas, addXsuaa, hasXsuaa, hasIas, addMultitenancy, hasMTXRoute, hasMTXRouteJava,
38
38
  addMta, addHelm, addHelmUnifiedRuntime, hasMta, hasHelm, hasHelmUnifiedRuntime,
39
39
  addContainerize, srvPath, approuterPath
40
40
  } = project
41
41
 
42
42
 
43
- if (addMta || (addMultitenancy || addXsuaa) && hasMta) {
43
+ if (addMta || (addMultitenancy || addXsuaa || addIas) && hasMta) {
44
44
  const srv = srv4(srvPath)
45
45
  const apis = isJava && addMultitenancy ? [requiredJavaApprouterApi4(srv), providedJavaApprouterApi] : []
46
46
  const mtxSidecar = mtxSidecar4(isJava ? 'mtx/sidecar' : 'gen/mtx/sidecar')
@@ -49,15 +49,21 @@ module.exports = class ApprouterTemplate extends require('../../plugin') {
49
49
  const modules = [srv, approuter]
50
50
  if (addMultitenancy) modules.push(mtxSidecar)
51
51
  const additions = [...modules, ...apis]
52
- if (hasXsuaa) additions.push(xsuaa)
53
52
  const relationships = []
54
- if (hasXsuaa) relationships.push({
55
- insert: [xsuaa, 'name'],
56
- into: [srv, 'requires', 'name']
57
- }, {
58
- insert: [xsuaa, 'name'],
59
- into: [approuter, 'requires', 'name']
60
- })
53
+ if (hasXsuaa) {
54
+ additions.push(xsuaa)
55
+ relationships.push({
56
+ insert: [xsuaa, 'name'],
57
+ into: [approuter, 'requires', 'name']
58
+ })
59
+ }
60
+ if (hasIas) {
61
+ additions.push(ias)
62
+ relationships.push({
63
+ insert: [ias, 'name'],
64
+ into: [approuter, 'requires', 'name']
65
+ })
66
+ }
61
67
  await merge(__dirname, 'files/mta.yaml.hbs').into('mta.yaml', {
62
68
  with: project, additions, relationships,
63
69
  })
@@ -33,7 +33,7 @@ module.exports = class AuditLoggingTemplate extends require('../../plugin') {
33
33
 
34
34
  async combine() {
35
35
  const project = readProject()
36
- const { addMta, addHelm, addMultitenancy, srvPath } = project
36
+ const { addMta, addHelm, addHelmUnifiedRuntime, addMultitenancy, srvPath } = project
37
37
  const { plan = 'premium' } = cds.cli.options
38
38
  project.servicePlan = plan
39
39
 
@@ -53,7 +53,7 @@ module.exports = class AuditLoggingTemplate extends require('../../plugin') {
53
53
  ]
54
54
  })
55
55
  }
56
- if (addHelm) {
56
+ if (addHelm || addHelmUnifiedRuntime) {
57
57
  await merge({
58
58
  srv: { bindings: { auditlog: { serviceInstanceName: 'auditlog' }}},
59
59
  auditlog: {
@@ -2,9 +2,8 @@
2
2
  _schema-version: '1.0'
3
3
  repository: {{& imageRegistry}}
4
4
  tag: latest
5
- before-all:
6
- - npx cds build --production
7
5
  {{#isJava}}
6
+ before-all:
8
7
  - mvn clean package -DskipTests=true --batch-mode
9
8
  {{/isJava}}
10
9
  modules:
@@ -26,10 +26,6 @@ module.exports = class DestinationTemplate extends require('../../plugin') {
26
26
  const project = readProject()
27
27
  const { addHelm, addApprouter, addMultitenancy, addHelmUnifiedRuntime, addMta, hasMta, srvPath, isJava } = project
28
28
 
29
- if (addMultitenancy) {
30
- await merge({ cds: { requires: { destinations:true } } }).into('mtx/sidecar/package.json')
31
- }
32
-
33
29
  if (addMta || (addApprouter || addMultitenancy) && hasMta) {
34
30
  const srv = srv4(srvPath)
35
31
  const additions = [srv, destination]
@@ -45,8 +45,6 @@ runs:
45
45
  shell: bash
46
46
  - run: curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
47
47
  shell: bash
48
- - run: npm install -g ctz
49
- shell: bash
50
48
  - shell: bash
51
49
  run: |
52
50
  mkdir ${HOME}/kyma-binaries
@@ -44,9 +44,9 @@ module.exports = class HanaTemplate extends require('../../plugin') {
44
44
 
45
45
  async combine() {
46
46
  const project = readProject()
47
- const { addMta, addHelm, addHelmUnifiedRuntime, addMultitenancy, addContainerize, isJava, hasHelm, hasMultitenancy, srvPath } = project
47
+ const { addMta, hasMta, addHelm, addHelmUnifiedRuntime, addMultitenancy, addContainerize, isJava, hasHelm, hasHelmUnifiedRuntime, hasMultitenancy, srvPath } = project
48
48
 
49
- if (addMta || addMultitenancy) {
49
+ if (addMta || (addMultitenancy && hasMta)) {
50
50
  const srv = srv4(srvPath)
51
51
  const db = hasMultitenancy ? serviceManager : hdiContainer
52
52
  const deletions = hasMultitenancy ? [{
@@ -91,7 +91,7 @@ module.exports = class HanaTemplate extends require('../../plugin') {
91
91
 
92
92
  }
93
93
 
94
- if (addMultitenancy && hasHelm) {
94
+ if (addMultitenancy && (hasHelm || hasHelmUnifiedRuntime)) {
95
95
  await merge('chart/values.yaml')
96
96
  .remove(['hana-deployer', 'srv.bindings.db', 'hana'])
97
97
 
@@ -1,7 +1,6 @@
1
1
  const { join } = require('path')
2
- const yaml = require('yaml')
3
2
  const cds = require('../../../cds')
4
- const { exists, rimraf, copy, readFileSync } = cds.utils
3
+ const { readFileSync, exists, rimraf, copy, yaml } = cds.utils
5
4
  const { readProject } = require('../../projectReader')
6
5
  const { merge } = require('../../merge')
7
6
  const { ask4 } = require('../../../util/question')
@@ -37,47 +36,36 @@ module.exports = class HelmTemplate extends require('../../plugin') {
37
36
  // Won't need this any more with plugin approach: package not there -> plugin not pulled
38
37
  static hasInProduction() {
39
38
  if (exists('chart/Chart.yaml')) {
40
- const chartFile = readFileSync(join(cds.root, 'chart', 'Chart.yaml'), 'utf8')
41
- const chartYaml = yaml.parseDocument(chartFile).toJS()
39
+ const chartYaml = yaml.parse(readFileSync(join(cds.root, 'chart', 'Chart.yaml'), 'utf8'))
42
40
  return chartYaml.annotations?.["app.kubernetes.io/managed-by"] === 'cds-dk/helm'
43
- }
44
- return false
41
+ }
42
+ return false
45
43
  }
46
44
 
47
45
  async run() {
48
46
  const project = readProject()
49
- project.domain = 'abc.com'
50
- project.registry = 'registry-server'
51
- project.imagePullSecret = 'docker-registry'
52
- project.tag = 'latest'
53
-
54
- if (cds.cli.command === 'add' && !HelmTemplate.hasInProduction()) {
55
- let registry = 'registry-server', tag = 'latest', domain = 'abc.com'
56
-
47
+ let imagePullSecret, registry, domain, tag
48
+ if (!HelmTemplate.hasInProduction() && !project.isBas /* don't run this for the BAS Yeoman generator */) {
57
49
  try {
58
50
  const domainGuess = execSync(`kubectl config view --minify --output jsonpath="{.clusters[*].cluster.server}"`, { stdio: "pipe" }).toString()
59
51
  const domainStartIndex = domainGuess.indexOf('api.')
60
52
  if (domainStartIndex !== -1) domain = domainGuess.substring(domainStartIndex + 4)
61
53
  } catch (error) { /* ignore */ }
62
54
 
63
- // read project.registry
64
55
  if (exists('containerize.yaml')) {
65
- const containerizeYamlFile = readFileSync(join(cds.root, 'containerize.yaml'), 'utf8')
66
- const containerizeYaml = yaml.parseDocument(containerizeYamlFile).toJS()
67
- registry = containerizeYaml.repository
68
- tag = containerizeYaml.tag
56
+ const { repository: r, tag: t } = yaml.parse(join(cds.root, 'containerize.yaml'))
57
+ if (r) registry = r
58
+ if (t) tag = t
69
59
  }
70
-
71
- if (!cds.cli.options['y']) {
72
- const [registryAns] = await ask4(`registry server (${registry}): `)
73
- registry = registryAns || registry
60
+ if (!cds.cli.options.y && !cds.cli.options.force) {
61
+ registry ??= await ask4(`registry server: `)
62
+ domain ??= await ask4(`cluster domain: `)
74
63
  }
75
-
76
- project.domain = domain
77
- project.registry = registry
78
- project.tag = tag
79
64
  }
80
-
65
+ project.registry = registry ?? '<registry-server>'
66
+ project.domain = domain ?? '<cluster-domain>'
67
+ project.imagePullSecret = imagePullSecret ?? 'docker-registry'
68
+ project.tag = tag ?? 'latest'
81
69
  await copy(join(__dirname, 'files', 'values.schema.json')).to('chart/values.schema.json')
82
70
  await merge(__dirname, 'files/Chart.yaml.hbs').into('chart/Chart.yaml', { with: project })
83
71
  await merge(__dirname, 'files/values.yaml.hbs').into('chart/values.yaml', { with: project })
@@ -1,13 +1,4 @@
1
1
  ---
2
- before-all:
3
- {{#hasUI5}}
4
- - mkdir -p app/html5-deployer/resources
5
- {{#apps}}
6
- - npm run build --prefix app/{{app}}
7
- # zip filename should match the archiveName specified in ui5-deploy.yaml
8
- - cp -f app/{{app}}/dist/{{app}}.zip app/html5-deployer/resources/{{app}}.zip
9
- {{/apps}}
10
- {{/hasUI5}}
11
2
  modules:
12
3
  - name: {{appName}}-html5-deployer
13
4
  build-parameters:
@@ -28,13 +28,6 @@ modules:
28
28
  {{#hasXsuaa}}
29
29
  - name: {{appName}}-auth
30
30
  {{/hasXsuaa}}
31
- {{#hasIas}}
32
- - name: {{appName}}-ias
33
- parameters:
34
- config:
35
- credential-type: X509_GENERATED
36
- app-identifier: app-deployer
37
- {{/hasIas}}
38
31
  - name: srv-api
39
32
  - name: {{appName}}-html5-repo-host
40
33
  parameters:
@@ -53,3 +53,9 @@ approuter:
53
53
  html5-apps-repo-runtime:
54
54
  serviceInstanceName: html5-apps-repo-runtime
55
55
  {{/hasApprouter}}
56
+ {{#hasMultitenancy}}
57
+ sidecar:
58
+ bindings:
59
+ html5-apps-repo-host:
60
+ serviceInstanceName: html5-apps-repo-host
61
+ {{/hasMultitenancy}}
@@ -7,13 +7,15 @@
7
7
  "target": "/odata/$1",
8
8
  "destination": "srv-api",
9
9
  {{#hasXsuaa}}"authenticationType": "xsuaa",{{/hasXsuaa}}
10
+ {{#hasIas}}"authenticationType": "ias",{{/hasIas}}
10
11
  "csrfProtection": true
11
12
  },
12
13
  {
13
14
  "source": "^(.*)$",
14
- "target": "$1",
15
- "service": "html5-apps-repo-rt"{{#hasXsuaa}},
16
- "authenticationType": "xsuaa"{{/hasXsuaa}}
15
+ "service": "html5-apps-repo-rt",
16
+ {{#hasXsuaa}}"authenticationType": "xsuaa",{{/hasXsuaa}}
17
+ {{#hasIas}}"authenticationType": "ias",{{/hasIas}}
18
+ "target": "$1"
17
19
  }
18
20
  ]
19
21
  }
@@ -2,7 +2,7 @@ const { join } = require('path')
2
2
  const { readProject } = require('../../projectReader')
3
3
  const { merge } = require('../../merge')
4
4
  const { srv4, html5RepoHost, html5Runtime, xsuaa, appDeployer, approuter, mtxSidecar4 } = require('../../registries/mta')
5
- const { odataApi } = require('../../registries/xs-app')
5
+ const { odataApi, html5RepoApi } = require('../../registries/xs-app')
6
6
  const { exists } = require('../../../cds').utils
7
7
  const { renderAndCopy } = require('../../template')
8
8
 
@@ -44,7 +44,7 @@ module.exports = class Html5RepoTemplate extends require('../../plugin') {
44
44
  await Promise.all(apps.map(async ({ app }) => {
45
45
  project.app = app
46
46
  project.hasIndexHtml = exists(join(appPath, app, 'webapp/index.html'))
47
- const additions = [odataApi]
47
+ const additions = [odataApi, html5RepoApi]
48
48
  await merge(__dirname, 'files/xs-app.json.hbs').into(join(appPath, app, 'xs-app.json'), { with: project, additions })
49
49
  await merge(__dirname, 'files/app-package.json.hbs').into(join(appPath, app, 'package.json'), { project })
50
50
  }))
@@ -1,6 +1,6 @@
1
1
  const { readProject } = require('../../projectReader')
2
2
  const { merge, sort } = require('../../merge')
3
- const { srv4, approuter, mtxSidecar4 } = require('../../registries/mta')
3
+ const { srv4, approuter, ias, mtxSidecar4 } = require('../../registries/mta')
4
4
  const cds = require('../../../cds')
5
5
  const { exists, read } = cds.utils
6
6
 
@@ -28,7 +28,7 @@ module.exports = class IasTemplate extends require('../../plugin') {
28
28
 
29
29
  async combine() {
30
30
  const project = readProject()
31
- const { isJava, addMta, addCfManifest, addHelm, addHelmUnifiedRuntime, addApprouter, addMultitenancy, srvPath } = project
31
+ const { isJava, addMta, hasMta, addCfManifest, addHelm, addHelmUnifiedRuntime, addApprouter, addMultitenancy, srvPath } = project
32
32
 
33
33
  if (addMultitenancy) {
34
34
  const conf = exists('mtx/sidecar/package.json') ? await read('mtx/sidecar/package.json') : {}
@@ -43,12 +43,11 @@ module.exports = class IasTemplate extends require('../../plugin') {
43
43
  await merge(__dirname, 'files/sidecar.package.json.hbs').into('mtx/sidecar/package.json')
44
44
  }
45
45
 
46
- if (addMta || addApprouter || addMultitenancy) {
46
+ if (addMta || (addApprouter || addMultitenancy) && hasMta) {
47
47
  const srv = srv4(srvPath)
48
- const identity = { in: 'resources', where: { 'parameters.service': 'identity' } }
49
48
  const srvIdentityBinding = { in: [srv, 'requires'], where: { 'parameters.config.app-identifier': 'srv' } }
50
49
  const srvCertUrlApi = { in: [srv, 'provides'], where: { 'name': 'srv-api' } }
51
- const additions = [srv, identity, srvIdentityBinding, srvCertUrlApi]
50
+ const additions = [srv, ias, srvIdentityBinding, srvCertUrlApi]
52
51
  const overwrites = []
53
52
 
54
53
  if (addApprouter) {
@@ -42,12 +42,12 @@ module.exports = class MultitenancyTemplate extends require('../../plugin') {
42
42
  async combine() {
43
43
  const project = readProject()
44
44
  const {
45
- isNodejs, isJava, addMta, addHelm, addHelmUnifiedRuntime,
45
+ isNodejs, isJava, addMta, hasMta, addHelm, addHelmUnifiedRuntime,
46
46
  addXsuaa, hasXsuaa, addIas, hasIas, hasHana, addContainerize, hasApprouter,
47
47
  srvPath, appName
48
48
  } = project
49
49
 
50
- if (addMta || addIas || addXsuaa) {
50
+ if (addMta || (addIas || addXsuaa) && hasMta) {
51
51
  const srv = srv4(srvPath)
52
52
  const mtxSidecar = mtxSidecar4(isJava ? 'mtx/sidecar' : 'gen/mtx/sidecar')
53
53
  const modules = [srv, mtxSidecar]
@@ -24,12 +24,16 @@ module.exports = class PipelineTemplate extends require('../../plugin') {
24
24
  }
25
25
 
26
26
  async run() {
27
- await copy(join(__dirname, 'files')).to(cds.root)
27
+ await Promise.all([
28
+ copy(join(__dirname, 'files', '.pipeline')).to(join(cds.root, '.pipeline')),
29
+ copy(join(__dirname, 'files', 'Jenkinsfile')).to(join(cds.root, 'Jenkinsfile'))
30
+ ])
28
31
 
29
32
  const project = readProject()
30
33
  const { apps, appPath, hasUI5 } = project
31
34
 
32
35
  if (hasUI5) { // UI5 integration might be needed even without HTML5 repo
36
+ console.log('Adding UI5 integration to the pipeline for the detected UI5 apps...')
33
37
  for (const { app } of apps) {
34
38
  project.app = app
35
39
  await Promise.all([
@@ -33,14 +33,14 @@ module.exports = class XsuaaTemplate extends require('../../plugin') {
33
33
 
34
34
  async combine() {
35
35
  const project = readProject()
36
- const { addMultitenancy, addMta, hasMta, addHelm, hasHelm, addHelmUnifiedRuntime, isJava, srvPath } = project
36
+ const { addMultitenancy, addMta, hasMta, addHelm, hasHelm, hasHelmUnifiedRuntime, addHelmUnifiedRuntime, isJava, srvPath } = project
37
37
 
38
38
  const cds = require('../../../../lib')
39
39
  const xsSecurity = await (async () => {
40
40
  try {
41
41
  const models = await cds.load(cds.env.folders.srv)
42
42
  return cds.compile.to.xsuaa(models)
43
- } catch (error) { /* ignore */ }
43
+ } catch { /* ignore */ }
44
44
  })() ?? { scopes: [], attributes: [], 'role-templates': [], 'authorities-inheritance': false }
45
45
 
46
46
  const additions = xsSecurity.scopes.map(scope => ({ in: 'scopes', where: { name: scope.name } }))
@@ -84,7 +84,7 @@ module.exports = class XsuaaTemplate extends require('../../plugin') {
84
84
  await merge(__dirname, 'files/mta.yaml.hbs').into('mta.yaml', { project, additions, overwrites, relationships })
85
85
  }
86
86
 
87
- if (addHelm || addHelmUnifiedRuntime || addMultitenancy && hasHelm) {
87
+ if (addHelm || addHelmUnifiedRuntime || addMultitenancy && (hasHelm || hasHelmUnifiedRuntime)) {
88
88
  const overwrites = addMultitenancy ? [{ item: 'xsuaa.parameters.tenant-mode', withValue: 'shared' }] : []
89
89
  await merge(__dirname, 'files/values.yaml.hbs').into('chart/values.yaml', { with: project, overwrites })
90
90
  await merge({
@@ -25,7 +25,7 @@ async function ask4(...args) {
25
25
  await askQuestion(question)
26
26
  }
27
27
  rl.close()
28
- return answers
28
+ return Array.isArray(args) ? answers[0] : answers
29
29
  }
30
30
 
31
31
  module.exports = { ask4 }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@sap/cds-dk",
3
- "version": "9.4.2",
3
+ "version": "9.4.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@sap/cds-dk",
9
- "version": "9.4.2",
9
+ "version": "9.4.3",
10
10
  "license": "SEE LICENSE IN LICENSE",
11
11
  "dependencies": {
12
12
  "@cap-js/asyncapi": "^1.0.0",
@@ -42,8 +42,8 @@
42
42
  }
43
43
  },
44
44
  "node_modules/@cap-js/db-service": {
45
- "version": "2.5.1",
46
- "integrity": "sha512-dpz9lvOepcXOgE8uTs9abCotLcCt1odpwlrUT4AW2pnSrMB0EVMRbHKVIlufmrm8Kh/Y5c2d6ODald8eahsEDw==",
45
+ "version": "2.6.0",
46
+ "integrity": "sha512-t72/FcAYFbPdx+5iV+lVKcwF2MLOx8II3jJdlC1dX/KXQORoS3wDFwWbakP0f/eharE5hfa7KMFJqrSMtDigbQ==",
47
47
  "devOptional": true,
48
48
  "license": "Apache-2.0",
49
49
  "dependencies": {
@@ -65,12 +65,12 @@
65
65
  }
66
66
  },
67
67
  "node_modules/@cap-js/sqlite": {
68
- "version": "2.0.3",
69
- "integrity": "sha512-Zm2JLpPuddFkeqM47TX7V7IC6x3WOfn5lTRbaiVOMvRQoshCNXTkjv083ssfiwF/jF/46/PSVox+fTElozg6XA==",
68
+ "version": "2.0.4",
69
+ "integrity": "sha512-QPVkycLJG6EubtjrPeiK4dTI1zPH/nabvhiYnTeg2AbeQ8mbazm5pjmcLrzOOKF/5bGS8KQo2J+49fU5LPRR3A==",
70
70
  "license": "Apache-2.0",
71
71
  "optional": true,
72
72
  "dependencies": {
73
- "@cap-js/db-service": "^2",
73
+ "@cap-js/db-service": "^2.6.0",
74
74
  "better-sqlite3": "^12.0.0"
75
75
  },
76
76
  "peerDependencies": {
@@ -120,8 +120,8 @@
120
120
  }
121
121
  },
122
122
  "node_modules/@sap/cds-compiler": {
123
- "version": "6.4.2",
124
- "integrity": "sha512-kjhq8GxzCLIfsM4aln9saxkifPC26MYuAqT82QB7RGZDVFM5Pw09Dp6TcKCSp65hut0YgHkvHlVGWMZWWBJNdA==",
123
+ "version": "6.4.6",
124
+ "integrity": "sha512-auAjRh9t0KKj4LiGAr/fxikZRIngx9YXVHTJWf0LeaGv0ZpYOi6iWbSnU1XRB2e6hsf+Ou1w5oTOHooC5sZfog==",
125
125
  "license": "SEE LICENSE IN LICENSE",
126
126
  "bin": {
127
127
  "cdsc": "bin/cdsc.js",
@@ -133,8 +133,8 @@
133
133
  }
134
134
  },
135
135
  "node_modules/@sap/cds-fiori": {
136
- "version": "2.1.0",
137
- "integrity": "sha512-EoNtPT5aVurxwsqHQfW2FgnXA3Ysu9GvihR0jTQNUZ4pVlHHFkfFehH6G2DIHKyfTROWaahegJsMpiwpE8clkg==",
136
+ "version": "2.1.1",
137
+ "integrity": "sha512-X+4v4LBAT8HIt0zr28/kJNS15nlNlcM97vAMW+agLrmK134nyBiMwUMcp8BMhxlG9B2PykrnAKH56D9O3tfoBg==",
138
138
  "license": "SEE LICENSE IN LICENSE",
139
139
  "peerDependencies": {
140
140
  "@sap/cds": ">=8",
@@ -142,8 +142,8 @@
142
142
  }
143
143
  },
144
144
  "node_modules/@sap/cds-mtxs": {
145
- "version": "3.4.2",
146
- "integrity": "sha512-bpKHKbehDSZpe9IoY1Vn2LQ5kgQns6RdlMsTOE9rw9zP//Yq5hFtl7SKFEM0A0/+WL3E+Rp/7Y2tc76erKYqdA==",
145
+ "version": "3.4.3",
146
+ "integrity": "sha512-vgABFr7huaKWGx2fWHeGom5bVgsQKD7/gqkC7aQ/7yC9hdZdrx0mz4iZ0ASHUZ5PZWp2FWLD+eaJ9sXKUGHgpA==",
147
147
  "license": "SEE LICENSE IN LICENSE",
148
148
  "dependencies": {
149
149
  "@sap/hdi-deploy": "^5"
@@ -261,8 +261,8 @@
261
261
  "license": "MIT"
262
262
  },
263
263
  "node_modules/axios": {
264
- "version": "1.12.2",
265
- "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
264
+ "version": "1.13.1",
265
+ "integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==",
266
266
  "license": "MIT",
267
267
  "dependencies": {
268
268
  "follow-redirects": "^1.15.6",
@@ -1213,8 +1213,8 @@
1213
1213
  "license": "MIT"
1214
1214
  },
1215
1215
  "node_modules/node-abi": {
1216
- "version": "3.78.0",
1217
- "integrity": "sha512-E2wEyrgX/CqvicaQYU3Ze1PFGjc4QYPGsjUrlYkqAE0WjHEZwgOsGMPMzkMse4LjJbDmaEuDX3CM036j5K2DSQ==",
1216
+ "version": "3.79.0",
1217
+ "integrity": "sha512-Pr/5KdBQGG8TirdkS0qN3B+f3eo8zTOfZQWAxHoJqopMz2/uvRnG+S4fWu/6AZxKei2CP2p/psdQ5HFC2Ap5BA==",
1218
1218
  "license": "MIT",
1219
1219
  "optional": true,
1220
1220
  "dependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-dk",
3
- "version": "9.4.2",
3
+ "version": "9.4.3",
4
4
  "description": "Command line client and development toolkit for the SAP Cloud Application Programming Model",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [