@sap/cds-dk 9.2.0 → 9.3.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/bin/bind.js +7 -8
  3. package/bin/up.js +13 -9
  4. package/lib/bind/cf.js +4 -0
  5. package/lib/bind/k8s.js +21 -0
  6. package/lib/bind/plugin.js +9 -0
  7. package/lib/bind/shared.js +4 -2
  8. package/lib/build/buildTaskProviderFactory.js +8 -14
  9. package/lib/build/constants.js +1 -0
  10. package/lib/build/index.js +17 -2
  11. package/lib/build/plugin.js +5 -7
  12. package/lib/build/provider/hana/index.js +4 -4
  13. package/lib/compile/to-xsuaa.js +11 -2
  14. package/lib/deploy/to-hana/hana.js +1 -1
  15. package/lib/deploy/to-hana/hdiDeployUtil.js +1 -1
  16. package/lib/env/schemas/cds-rc.js +2 -2
  17. package/lib/init/constants.js +3 -2
  18. package/lib/init/projectReader.js +5 -5
  19. package/lib/init/template/ams/index.js +1 -2
  20. package/lib/init/template/approuter/files/containerize.yaml.hbs +1 -1
  21. package/lib/init/template/approuter/index.js +1 -1
  22. package/lib/init/template/attachments/index.js +1 -1
  23. package/lib/init/template/audit-logging/index.js +1 -1
  24. package/lib/init/template/extension/files/.base/index.cds +6 -6
  25. package/lib/init/template/extension/index.js +5 -5
  26. package/lib/init/template/github-actions/files/{cf-github-deploy → github-deployment}/action.yaml +7 -5
  27. package/lib/init/template/github-actions/files/kyma-info/action.yaml +1 -1
  28. package/lib/init/template/github-actions/files/workflows/cf.yaml.hbs +14 -7
  29. package/lib/init/template/github-actions/files/workflows/kyma.yaml.hbs +6 -3
  30. package/lib/init/template/github-actions/files/workflows/test.yaml.hbs +5 -2
  31. package/lib/init/template/github-actions/index.js +8 -13
  32. package/lib/init/template/hana/index.js +2 -1
  33. package/lib/init/template/html5-repo/files/mta.yaml.hbs +5 -1
  34. package/lib/init/template/html5-repo/files/package.json.hbs +1 -1
  35. package/lib/init/template/html5-repo/index.js +24 -19
  36. package/lib/init/template/ias/files/mta.yaml.hbs +4 -3
  37. package/lib/init/template/ias/files/values.yaml.hbs +41 -40
  38. package/lib/init/template/lint/index.js +6 -11
  39. package/lib/init/template/mta/files/package.json +1 -1
  40. package/lib/init/template/nodejs/files/.vscode/extensions.json +0 -1
  41. package/lib/init/template/nodejs/files/.vscode/settings.json +2 -2
  42. package/lib/init/template/nodejs/files/package.json.hbs +3 -9
  43. package/lib/init/template/notifications/index.js +1 -1
  44. package/lib/init/template/portal/files/mta.yaml.hbs +5 -1
  45. package/lib/init/template/portal/index.js +0 -1
  46. package/lib/init/template/postgres/index.js +1 -1
  47. package/lib/init/template/sample/files/app/_i18n/i18n.properties +15 -0
  48. package/lib/init/template/sample/files/app/_i18n/i18n_de.properties +15 -0
  49. package/lib/init/template/sample/files/app/admin-books/fiori-service.cds +14 -15
  50. package/lib/init/template/sample/files/app/browse/fiori-service.cds +44 -44
  51. package/lib/init/template/sample/files/app/common.cds +99 -84
  52. package/lib/init/template/sample/files/db/schema.cds +22 -17
  53. package/lib/init/template/sample/files/package.json.hbs +1 -3
  54. package/lib/init/template/sample/files/srv/admin-service.cds +4 -3
  55. package/lib/init/template/sample/files/srv/cat-service.cds +28 -11
  56. package/lib/init/template/tiny-sample/files/nodejs/db/schema.cds +3 -3
  57. package/lib/init/template/typescript/files/package.json +1 -1
  58. package/lib/init/template/ui5/index.js +22 -0
  59. package/lib/init/template/workzone-standard/index.js +1 -1
  60. package/lib/lint/eslintHelp.json +5 -3
  61. package/lib/util/cf.js +7 -7
  62. package/npm-shrinkwrap.json +17 -17
  63. package/package.json +1 -1
  64. package/lib/init/template/sample/files/javascript/admin-service.js.hbs +0 -24
  65. package/lib/init/template/sample/files/javascript/cat-service.js.hbs +0 -43
  66. package/lib/init/template/sample/files/typescript/admin-service.ts.hbs +0 -16
  67. package/lib/init/template/sample/files/typescript/cat-service.ts.hbs +0 -38
package/CHANGELOG.md CHANGED
@@ -6,6 +6,42 @@ 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.3.0 - 2025-09-01
10
+
11
+ ### Added
12
+
13
+ - `cds bind -a` is now also supported for Kyma, where the app prefix can be passed for `-a`, e.g. `bookshop-srv`.
14
+ - `cds build --for hana` now trims leading and trailing whitespaces in csv-Files if build option `trimCsvWhitespaces` is set.
15
+ - `cds add github-actions` sets required `permissions`.
16
+ - `cds up` supports a deployment layout where approuter or portal service configuration is in a top-level `.deploy` folder.
17
+ - `cds add ias` sets the `access-token-format` to `jwt` by default.
18
+
19
+ ### Changed
20
+
21
+ - `cds add sample` provides more i18n translations.
22
+ - `cds add ias` sets the `xsuaa-cross-consumption` field to `true` by default.
23
+
24
+ ### Fixed
25
+
26
+ - `cds add ias` adds a subdomain-less application URL in `redirect_uris`.
27
+
28
+ ## Version 9.2.1 - 2025-08-22
29
+
30
+ ### Changed
31
+
32
+ - `cds add github-actions` adds `if: always()` to the scripts retrieving Cloud Foundry logs.
33
+ - `cds up` uses a default timeout of 10 minutes for Helm upgrades.
34
+ - `cds bind` works out-of-the-box for PostgreSQL databases.
35
+ - `cds bind -a` gives warning if no services are bound to the targeted app.
36
+
37
+ ### Fixed
38
+
39
+ - `cds add github-actions` won't try to merge a `cf-info` action if there's an existing `mta.yaml`.
40
+ - `cds add workzone` with missing `sap.app` config in `manifest.json` does not throw a `TypeError`.
41
+ - `cds compile --to xsuaa` generates roles for `@requires` in bound actions.
42
+ - `cds add lint` now adds proper configuration to enable linting of JavaScript and TypeScript files in VS Code.
43
+ - `cds add mta` does no longer adds services in `mta.yaml` for plugins coming from `devDependencies`.
44
+
9
45
  ## Version 9.2.0 - 2025-07-31
10
46
 
11
47
  ### Added
package/bin/bind.js CHANGED
@@ -46,7 +46,7 @@ module.exports = Object.assign(cds_bind, {
46
46
 
47
47
  *-a | --to-app-services* <app>
48
48
 
49
- Bind services of a given application (Cloud Foundry only).
49
+ Bind services of a given application.
50
50
 
51
51
  *--no-create-service-key*
52
52
 
@@ -83,7 +83,7 @@ const { bind, storeBindings } = require('../lib/bind/shared');
83
83
 
84
84
 
85
85
  async function cds_bind(args, options) {
86
- const { on, 'to-app-services': app, to, out, 'output-file': outputFile, exec, credentials } = options
86
+ const { on = 'cf', 'to-app-services': app, to, out, 'output-file': outputFile, exec, credentials } = options
87
87
 
88
88
  if (exec) {
89
89
  process.env.CDS_ENV ??= 'hybrid'
@@ -103,10 +103,6 @@ async function cds_bind(args, options) {
103
103
  options.out ??= outputFile
104
104
  }
105
105
 
106
- if (on === 'k8s' && app) {
107
- throw `Option ${term.bold('--to-app-services')} is only supported for Cloud Foundry.`;
108
- }
109
-
110
106
  if (credentials === true) {
111
107
  throw `Option ${term.bold('--credentials')} requires a JSON object or a file path.`;
112
108
  }
@@ -155,9 +151,12 @@ async function cds_bind(args, options) {
155
151
  options.for ??= 'hybrid';
156
152
 
157
153
  if (app) {
158
- const cds = require('../lib/cds');
154
+ const cds = require('../lib/cds')
159
155
  const resolvedServices = [], unresolvedServices = {}
160
- const services = (await require('../lib/util/cf').services(app)).filter(s => Object.keys(s.credentials).length > 0)
156
+ const plugin = require(`../lib/bind/${on}`)
157
+ const services = await plugin.services4(app)
158
+
159
+ if (!services) console.warn(`no services bound to app ${app} - nothing to bind`)
161
160
 
162
161
  services.forEach(service => {
163
162
  const type = service.label || service.type;
package/bin/up.js CHANGED
@@ -30,7 +30,7 @@ module.exports = Object.assign(up, {
30
30
  const { execSync, spawn } = require('node:child_process')
31
31
  const cds = require('../lib/cds')
32
32
  const { highlight, bold } = require('../lib/util/term')
33
- const { exists, fs, path } = cds.utils
33
+ const { exists, fs, path, path: { join } } = cds.utils
34
34
  const { readProject } = require('../lib/init/projectReader')
35
35
 
36
36
  const run = cmd => {
@@ -79,14 +79,18 @@ async function up() {
79
79
  if (exists('mtx/sidecar') && !exists('mtx/sidecar/package-lock.json')) {
80
80
  run('npm i --package-lock-only --prefix mtx/sidecar')
81
81
  }
82
- if (exists('app/router/package.json') && !exists('app/router/package-lock.json')) {
83
- run('npm i --package-lock-only --prefix app/router')
84
- }
85
- if (exists('app/portal/package.json') && !exists('app/portal/package-lock.json')) {
86
- run('npm i --package-lock-only --prefix app/portal')
82
+
83
+ const app = cds.env.folders.app
84
+ const legacyToPreferred = {
85
+ [join(app, 'router')]: '.deploy/app-router',
86
+ [join(app, 'portal')]: '.deploy/portal',
87
+ [join(app, 'html5-deployer')]: '.deploy/html5-deployer'
87
88
  }
88
- if (exists('app/html5-deployer/package.json') && !exists('app/html5-deployer/package-lock.json')) {
89
- run('npm i --package-lock-only --prefix app/html5-deployer')
89
+ for (const [legacy, preferred] of Object.entries(legacyToPreferred)) {
90
+ const root = exists(preferred) ? preferred : legacy
91
+ if (exists(join(root, 'package.json')) && !exists(join(root, 'package-lock.json'))) {
92
+ run(`npm i --package-lock-only --prefix ${root}`)
93
+ }
90
94
  }
91
95
 
92
96
  if (cds.cli.options.to === 'k8s') {
@@ -116,7 +120,7 @@ async function up() {
116
120
 
117
121
  run('npm i --package-lock')
118
122
  await runDetectingError('ctz containerize.yaml --log --push', /^\[?ERROR\]?.*/m)
119
- let helmUpgrade = `helm upgrade --install ${appName} ./gen/chart ${nsHelm} --wait --wait-for-jobs`
123
+ let helmUpgrade = `helm upgrade --install ${appName} ./gen/chart ${nsHelm} --wait --wait-for-jobs --timeout=10m`
120
124
  if (hasXsuaa) helmUpgrade += ' --set-file xsuaa.jsonParameters=xs-security.json'
121
125
  run(helmUpgrade)
122
126
  run(`kubectl rollout status deployment ${appName}-srv ${nsKubectl} --timeout=8m`)
package/lib/bind/cf.js CHANGED
@@ -33,6 +33,10 @@ module.exports = new class CFBind extends require('./plugin') {
33
33
  return service
34
34
  }
35
35
 
36
+ async services4(app) {
37
+ return (await cf.services(app)).filter(s => Object.keys(s.credentials).length > 0)
38
+ }
39
+
36
40
  async resolve(name, binding) {
37
41
  this.target ??= await cf.target()
38
42
  this.#checkApiEndpoint(name, binding)
package/lib/bind/k8s.js CHANGED
@@ -30,6 +30,27 @@ module.exports = new class K8SBind extends require('./plugin') {
30
30
  return service
31
31
  }
32
32
 
33
+ services4(app) {
34
+ const { execSync } = require('node:child_process')
35
+ const json = cmd => JSON.parse(execSync(cmd, { encoding: 'utf8' }))
36
+
37
+ const bindings = json('kubectl get servicebindings -o json').items
38
+ .filter(b => b.metadata.name.startsWith(app))
39
+ if (!bindings.length) throw `cannot find services with metadata name starting with '${app}'`
40
+
41
+ const offerings = Object.fromEntries(
42
+ json('kubectl get serviceinstances -o json').items
43
+ .map(i => [i.metadata.name, i.spec.serviceOfferingName])
44
+ )
45
+
46
+ for (const b of bindings) {
47
+ const offering = offerings[b.spec.serviceInstanceName]
48
+ b.label = b.type = offering
49
+ b.name = b.metadata.name
50
+ }
51
+ return bindings
52
+ }
53
+
33
54
  async resolve(name, binding) {
34
55
  const { context, namespace, cluster } = await this.#context4(binding)
35
56
 
@@ -36,6 +36,15 @@ module.exports = class BindPlugin {
36
36
  throw new Error('credentials4 not implemented')
37
37
  }
38
38
 
39
+ /**
40
+ * Returns the services for the given app
41
+ * @param {string} app - The name of the app
42
+ * @returns {Promise<Array>} A promise that resolves to an array of bound service names
43
+ */
44
+ services4(_app) {
45
+ throw new Error('services4 not implemented')
46
+ }
47
+
39
48
  /**
40
49
  * Resolves the binding configuration for the given service name
41
50
  * @param {string} name - The name of the service
@@ -23,7 +23,8 @@ const KindToRequiresNameMap = {
23
23
  'enterprise-messaging-shared': 'messaging',
24
24
  'redis-messaging': 'messaging',
25
25
  'multitenancy': 'multitenancy',
26
- 'portal': 'portal'
26
+ 'portal': 'portal',
27
+ 'postgres': 'db'
27
28
  }
28
29
 
29
30
  const PreferredKinds = {
@@ -77,7 +78,8 @@ async function bind(options = { on: 'cf' }) {
77
78
  resolved.name = options.serviceArg
78
79
  } else if (kind) {
79
80
  const requiresName = KindToRequiresNameMap[kind]
80
- if (!requiresName) throw new Error(`Unknown CDS service name for service ${bold(instance)}. Please specify as argument for ${bold('cds bind')}.`)
81
+
82
+ if (!requiresName) throw `unknown CDS service name for service ${bold(instance)} of kind ${bold(kind)} – please specify as argument for ${bold('cds bind')}`
81
83
  resolved.name = requiresName
82
84
  }
83
85
  resolved.name ??= `custom-service:${instance || secret}`
@@ -8,24 +8,13 @@ const BuildTaskProvider = require('./buildTaskProvider')
8
8
  const { BuildError, normalizePath } = require('./util')
9
9
  const DEBUG = cds.debug('cli|build')
10
10
 
11
- /**
12
- * @typedef {{
13
- * clean?: boolean,
14
- * outputMode?: number,
15
- * for?: string,
16
- * src?: string,
17
- * dest?: string,
18
- * } & {[key:string]:unknown}} BuildTaskProviderFactoryOptions
19
- */
20
-
21
11
  /**
22
- * TODO
23
- * @typedef {object} Task
12
+ * @typedef {import('./plugin').Task} Task
24
13
  */
25
14
 
26
15
  class BuildTaskProviderFactory {
27
16
  /**
28
- * @param {BuildTaskProviderFactoryOptions} options - build options
17
+ * @param {import('./index').BuildOptions} options - build options
29
18
  */
30
19
  constructor(options = {}) {
31
20
  options.outputMode = options.outputMode || OUTPUT_MODE_FILESYSTEM
@@ -43,7 +32,7 @@ class BuildTaskProviderFactory {
43
32
  }
44
33
  }
45
34
  })
46
- /** @type {{ options: BuildTaskProviderFactoryOptions, tasks: Task}} */
35
+ /** @type {{ options: import('./index').BuildOptions, tasks: Task[]}} */
47
36
  this._context = { options, tasks: [] } // resolved tasks
48
37
  }
49
38
  get options() {
@@ -112,6 +101,7 @@ class BuildTaskProviderFactory {
112
101
  * 'for' defines an alias for built-in plugins like 'hana', 'java', 'node', 'fiori' or 'mtx'.
113
102
  * 'use' defines the fully qualified module name of custom build plugins implementations.
114
103
  * @param {Task} task
104
+ * @returns {typeof import('./plugin')}
115
105
  */
116
106
  getPlugin(task) {
117
107
  const provider = this._getProvider(task)
@@ -227,6 +217,10 @@ class PluginBuildTaskProvider extends BuildTaskProvider {
227
217
  })
228
218
  }
229
219
 
220
+ /**
221
+ * @param {{for: string}} key
222
+ * @returns {Task}
223
+ */
230
224
  getTask(key) {
231
225
  // plugin entry is already validated, a taskDefaults default member exists
232
226
  const task = JSON.parse(JSON.stringify(this.plugins.get(key.for)?.taskDefaults ?? {}))
@@ -38,6 +38,7 @@ exports.OPTION_WS = "ws" // run a workspa
38
38
  exports.OPTION_WS_PACK = "ws-pack" // enable tarball based packaging of workspace dependencies
39
39
  exports.CSV_FILE_TARGET = "csvFileTarget" // target folder when copying CSV files to the deployment target folder ./db/src/gen/*, default is 'data'
40
40
  exports.CSV_FILE_DETECTION = "csvFileDetection" // detect CSV files based on CDS model location - ENABLED by default
41
+ exports.CSV_TRIM_WHITESPACES = "trimCsvWhitespaces" // trim whitespaces from CSV column names and values - DISABLED by default
41
42
  exports.EXTENSION_POINT_VALIDATION = "extensionPointValidation" // validate extension point restrictions defined by the extended SaaS app - ENABLED by default
42
43
  exports.MTX_SIDECAR_DB_VALIDATION = "mtxSidecarDbValidation" // validate consistency of app and sidecar db configuration - ENABLED by default
43
44
  exports.CONTINUE_UNRESOLVED_SCHEMA_CHANGES = "continueUnresolvedSchemaChanges" // build doesn't fail if unresolved HANA schema changes are detected
@@ -6,10 +6,25 @@ const { BuildError } = require('./util')
6
6
 
7
7
  module.exports = { build, register, Plugin, BuildError }
8
8
 
9
+ // reuse schema definition from cds-rc.js used for package.json code completion
10
+ /** @typedef{import('../env/schemas/cds-rc')['$defs']['buildTaskType']['enum'][number]} BuildFor */
11
+
12
+ // using (string & {}) prevents widening of `'x' | string` to just `string`
13
+ /**
14
+ * @typedef {{
15
+ * clean?: boolean,
16
+ * outputMode?: number,
17
+ * for?: BuildFor | (string & {}),
18
+ * src?: string,
19
+ * dest?: string,
20
+ * model?: string[]
21
+ * } & {[key:string]:unknown}} BuildOptions
22
+ */
23
+
9
24
  /**
10
25
  * Executes cds build in the directory defined by cds.root.
11
26
  *
12
- * @param {object} options - command options as defined by build command.
27
+ * @param {BuildOptions} options - command options as defined by build command.
13
28
  */
14
29
  async function build(options = {}) {
15
30
  if (!fs.existsSync(cds.root) || !cds.utils.isdir(cds.root)) {
@@ -31,4 +46,4 @@ async function build(options = {}) {
31
46
 
32
47
  function register(id, plugin) {
33
48
  return require('./plugins').register(id, plugin)
34
- }
49
+ }
@@ -21,7 +21,7 @@ const DEBUG = cds.debug('cli|build')
21
21
 
22
22
  /**
23
23
  * The build plugin creates the build output for a dedicated build task. It is uniquely identified
24
- * by the build task's `for<` or `use` property. The `use` property represents
24
+ * by the build task's `for<` or `use` property. The `use` property represents
25
25
  * the fully qualified node module path of the build plugin implementation.
26
26
  * The build task engine defines the following protocol. The methods are invoked in descending order:
27
27
  * - `init()` - optional
@@ -40,13 +40,11 @@ class Plugin {
40
40
 
41
41
  /**
42
42
  * @type {Task | undefined}
43
- * @private
44
43
  */
45
44
  _task
46
45
 
47
46
  /**
48
47
  * @type {Context | undefined}
49
- * @private
50
48
  */
51
49
  _context
52
50
 
@@ -69,7 +67,7 @@ class Plugin {
69
67
  */
70
68
  _messages = []
71
69
  /**
72
- * Returns a list of build and compiler messages created by this build plugin.
70
+ * Returns a list of build and compiler messages created by this build plugin.
73
71
  * Supported message severities are 'Info', 'Warning', and 'Error'.
74
72
  */
75
73
  get messages() {
@@ -176,9 +174,9 @@ class Plugin {
176
174
 
177
175
  /**
178
176
  * Asynchronously copies a single file or the entire directory structure from 'src' to 'dest', including subdirectories and files.
179
- *
177
+ *
180
178
  * Note: The file names are stored in the list of files written by this build plugin.
181
- *
179
+ *
182
180
  * @param {string} src - The absolute or relative source path of the file or directory to copy.
183
181
  * Relative paths will be resolved to this task's source path.
184
182
  * @returns {FileWriter}
@@ -216,7 +214,7 @@ class Plugin {
216
214
 
217
215
  /**
218
216
  * Adds the given user message and severity to the list of messages issued by this build task.
219
- *
217
+ *
220
218
  * User messages will be logged after CDS build has been finished based on the log-level that has been set.
221
219
  * By default messages with severity <em>warning</em> and <em>error</em> will be logged.
222
220
  * @param {string} message - the message text
@@ -74,7 +74,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
74
74
  await this._writePackageJson()
75
75
  }
76
76
 
77
- // copy native hana artifacts from other workspaces including .hdbmigrationtable files defined for
77
+ // copy native hana artifacts from other workspaces including .hdbmigrationtable files defined for
78
78
  // common model artifacts that are not part of the current project
79
79
  if (wsPaths.length || cds.cli.options?.[OPTION_WS] || hasOptionValue(this.task.options[OPTION_WS], true)) {
80
80
  // first is winning, e.g. a .hdiconfig file might exist in multiple workspaces
@@ -272,7 +272,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
272
272
 
273
273
  for (const file of csvFiles) {
274
274
  if (this.isStagingBuild() || file.startsWith(this.task.options.compileDest)) {
275
- await stripComments(file)
275
+ await stripComments(file, null, this.task.options.trimCsvWhitespaces)
276
276
  }
277
277
  }
278
278
  }
@@ -394,7 +394,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
394
394
  }
395
395
  }
396
396
  // The last-dev CSN shall only be updated, if all .hdbmigrationtable files could be successfully updated.
397
- // Of course, .hdbmigrationtable files already written would need to be manually reverted before the
397
+ // Of course, .hdbmigrationtable files already written would need to be manually reverted before the
398
398
  // cds build command is again executed.
399
399
  await Promise.all(promises)
400
400
 
@@ -578,7 +578,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
578
578
  /**
579
579
  * Returns all hana native artifacts located in db/src/** workspace folders.
580
580
  * @param {Array} workspacePaths Array of workspace paths, 'cds.root' is excluded from the list
581
- * @returns {Array} Array of workspace files, each entry contains the db src directory,
581
+ * @returns {Array} Array of workspace files, each entry contains the db src directory,
582
582
  * the relative file path and the file name.
583
583
  */
584
584
  async _getWorkspaceFiles(workspacePaths) {
@@ -60,8 +60,10 @@ module.exports = function compile_to_xsuaa (model, options) {
60
60
  return findUserAttrInExpression(expr)
61
61
  }
62
62
 
63
- cds.reflect(model).foreach(def => {
64
- var reqAnnotations = def['@requires']
63
+ const authAnnotations = def => {
64
+ if (!def) return
65
+
66
+ let reqAnnotations = def['@requires']
65
67
  if(!Array.isArray(reqAnnotations)) reqAnnotations=[reqAnnotations]
66
68
 
67
69
  reqAnnotations.forEach(scope => {
@@ -84,7 +86,14 @@ module.exports = function compile_to_xsuaa (model, options) {
84
86
  lattributes.forEach(attr => attributes[attr] = true)
85
87
  })
86
88
  }
89
+ }
90
+
91
+ cds.reflect(model).foreach(def => {
92
+ authAnnotations(def)
93
+ if (def.actions) Object.values(def.actions).forEach(authAnnotations)
94
+ if (def.functions) Object.values(def.functions).forEach(authAnnotations)
87
95
  })
96
+
88
97
  const roleTemplates = Object.keys(scopes).map((s) => {
89
98
  return {
90
99
  name: s,
@@ -250,7 +250,7 @@ spec:
250
250
  if (/[^\w-_]+/g.exec(cfServiceInstanceName)) {
251
251
  throw `Service name ${cfServiceInstanceName} must only contain alpha-numeric, hyphens, and underscores.`;
252
252
  }
253
- console.log(`Using container ${bold(cfServiceInstanceName)}`);
253
+ console.log(`using container ${bold(cfServiceInstanceName)}`);
254
254
 
255
255
  const cfConfig = cfServiceInstanceMta?.parameters?.config;
256
256
  const serviceInstance = await cfUtil.getOrCreateService('hana', 'hdi-shared', cfServiceInstanceName, cfConfig);
@@ -6,7 +6,7 @@ const HDI_DEPLOY_LIB = '@sap/hdi-deploy';
6
6
  module.exports = new class HdiDeployUtil {
7
7
 
8
8
  async deploy(dbDir, vcapEnv = {}, { autoUndeploy, parameter={} } = {}) {
9
- console.log(`Deploying to HANA from ${dbDir}`);
9
+ console.log(`deploying to HANA from ${dbDir}`);
10
10
 
11
11
  const { clean_env, deploy } = this._loadHdiDeployLib(dbDir);
12
12
 
@@ -278,7 +278,7 @@ module.exports = {
278
278
  },
279
279
 
280
280
  buildTaskType: {
281
- enum: [
281
+ enum: /**@type { const } */ ([
282
282
  "nodejs",
283
283
  "java",
284
284
  "hana",
@@ -288,7 +288,7 @@ module.exports = {
288
288
  "node-cf",
289
289
  "java-cf",
290
290
  "helm"
291
- ],
291
+ ]),
292
292
  enumDescriptions: [
293
293
  "Node.js build plugin",
294
294
  "Java build plugin",
@@ -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.1.1',
8
+ MAVEN_ARCHETYPE_VERSION: '4.2.0',
9
9
 
10
10
  OPTIONS: Object.freeze({
11
11
  COMPLETION: 'completion',
@@ -60,6 +60,7 @@ module.exports = {
60
60
  NOTIFICATIONS: 'notifications',
61
61
  WORKZONE: 'workzone',
62
62
  WORKZONE_STANDARD: 'workzone-standard',
63
+ UI5: 'ui5',
63
64
  DATA: 'data',
64
65
  HTTP: 'http',
65
66
  LINT: 'lint',
@@ -74,7 +75,7 @@ module.exports = {
74
75
 
75
76
  URLS: Object.freeze({
76
77
  PIPELINE_HELP: 'https://www.project-piper.io/',
77
- SAMPLES: 'https://github.com/SAP-samples/cloud-cap-samples',
78
+ SAMPLES: 'https://github.com/capire/samples',
78
79
  CAPIRE: 'https://cap.cloud.sap',
79
80
  MAVEN_ARCHETYPE_HELP: `https://cap.cloud.sap/docs/java/developing-applications/building#the-maven-archetype`,
80
81
  MAVEN_INSTALL_HELP: `https://maven.apache.org/install.html`,
@@ -47,12 +47,14 @@ module.exports = new class ProjectReader {
47
47
  * Returns cds.env using 'production' profile by default as mta deployment is executed with having production profile set.
48
48
  */
49
49
  env4(profile = 'production') {
50
+ const nodeEnv = process.env.NODE_ENV
50
51
  const cdsEnv = process.env.CDS_ENV
51
- process.env.CDS_ENV = profile
52
+ process.env.CDS_ENV = process.env.NODE_ENV = profile
52
53
  try {
53
54
  return cds.env.for('cds')
54
55
  } finally {
55
56
  cdsEnv ? process.env.CDS_ENV = cdsEnv : delete process.env.CDS_ENV
57
+ nodeEnv ? process.env.NODE_ENV = nodeEnv : delete process.env.NODE_ENV
56
58
  }
57
59
  }
58
60
 
@@ -122,7 +124,6 @@ module.exports = new class ProjectReader {
122
124
  const _isTypescript = () => !!pkgJson().dependencies?.typescript || !!pkgJson().devDependencies?.typescript
123
125
  const _hasTyper = () => !!pkgJson().dependencies?.['@cap-js/cds-typer'] || !!pkgJson().devDependencies?.['@cap-js/cds-typer']
124
126
  const _inProd = plugin => require(`./template/${plugin}`).hasInProduction(env) || cds.cli.argv.includes(plugin) || cds.cli.options.add?.has(plugin)
125
- const _ui5 = () => pkgJson().sapux
126
127
  const _uiEmbedded = () => exists(join(env.folders.app, 'xs-app.json'))
127
128
  const _uiModule = () => !_uiEmbedded() && isdir(env.folders.app) && fs.readdirSync(resolve(cds.root, env.folders.app)).length > 0
128
129
  const _isJava = () => this.isJava
@@ -132,6 +133,7 @@ module.exports = new class ProjectReader {
132
133
  }
133
134
  const reserved = {
134
135
  dependencies: () => pkgJson().dependencies,
136
+ devDependencies: () => pkgJson().devDependencies,
135
137
  has: x => _inProd(x),
136
138
  profile: () => cds.cli.options?.for,
137
139
  language: () => _isJava() ? 'java' : 'nodejs',
@@ -181,7 +183,7 @@ module.exports = new class ProjectReader {
181
183
  appPath: () => env.folders.app,
182
184
  appVersion: () => appVersion,
183
185
  jdkVersion: () => jdkVersion,
184
- approuterPath: () => (_uiEmbedded() ? join(env.folders.app) : join(env.folders.app, 'router')).replace(/\\/g, '/'),
186
+ approuterPath: () => (_uiEmbedded() ? join(env.folders.app) : exists('.deploy/app-router') ? join('.deploy', 'app-router') : join(env.folders.app, 'router')).replace(/\\/g, '/'),
185
187
  appName: () => appName,
186
188
  cleanedAppName: () => appName.replaceAll('_', '-'),
187
189
  strippedAppName: () => appName.replace(/-/g, ''),
@@ -190,8 +192,6 @@ module.exports = new class ProjectReader {
190
192
  hasUIEmbedded: _uiEmbedded,
191
193
  hasUIModule: _uiModule,
192
194
  hasUI: () => _uiEmbedded() || _uiModule(),
193
- isUI5: _ui5,
194
- hasUI5: _ui5,
195
195
  literal: () => () => text => text,
196
196
  hasMTXRoute: () => _inProd('extensibility') || (_inProd('multitenancy') && !_isJava()),
197
197
  hasMTXRouteJava: () => (_inProd('helm-unified-runtime') || _inProd('helm')) && _isJava() && _inProd('multitenancy'),
@@ -1,4 +1,3 @@
1
- const cds = require('../../../cds')
2
1
  const { readProject } = require('../../projectReader')
3
2
  const { merge, sort } = require('../../merge')
4
3
  const { srv4 } = require('../../registries/mta')
@@ -15,7 +14,7 @@ module.exports = class AmsTemplate extends require('../../plugin') {
15
14
  }
16
15
 
17
16
  static hasInProduction(env) {
18
- return env.requires?.auth?.ams || cds.cli.options?.add?.has('ams') || cds.cli.argv?.some(arg => arg?.match?.(/(^|,)ams(,|$)/))
17
+ return env.requires?.auth?.ams || readProject().devDependencies?.['@sap/ams']
19
18
  }
20
19
 
21
20
  async run() {
@@ -10,6 +10,6 @@ modules:
10
10
  type: nodejs
11
11
  builder: builder-jammy-base
12
12
  {{/hasHelmUnifiedRuntime}}
13
- path: app/router
13
+ path: {{& approuterPath}}
14
14
  env:
15
15
  BP_NODE_RUN_SCRIPTS: ""
@@ -19,7 +19,7 @@ module.exports = class ApprouterTemplate extends require('../../plugin') {
19
19
  }
20
20
 
21
21
  static hasInProduction(env) {
22
- return exists(join(env.folders.app, 'xs-app.json')) || exists(join(env.folders.app, 'router', 'xs-app.json'))
22
+ return exists(join('.deploy', 'app-router')) || exists(join(env.folders.app, 'xs-app.json')) || exists(join(env.folders.app, 'router', 'xs-app.json'))
23
23
  }
24
24
 
25
25
  async run() {
@@ -13,7 +13,7 @@ module.exports = class AttachmentsTemplate extends require('../../plugin') {
13
13
  }
14
14
 
15
15
  static hasInProduction(env) {
16
- return !!env.requires?.attachments
16
+ return !!env.requires?.attachments || readProject().dependencies?.['@cap-js/attachments']
17
17
  }
18
18
 
19
19
  async canRun() {
@@ -12,7 +12,7 @@ module.exports = class AuditLoggingTemplate extends require('../../plugin') {
12
12
  }
13
13
 
14
14
  static hasInProduction(env) {
15
- return env.requires?.['audit-log']
15
+ return env.requires?.['audit-log'] || readProject().dependencies?.['@cap-js/audit-logging']
16
16
  }
17
17
 
18
18
  options() {
@@ -1,7 +1,7 @@
1
1
  entity my.bookshop.Books {
2
- key ID : Integer;
3
- title : String @title : 'Title';
4
- stock : Integer @title : 'Stock';
2
+ key ID : Integer;
3
+ title : String @title: 'Title';
4
+ stock : Integer @title: 'Stock';
5
5
  }
6
6
 
7
7
  service CatalogService {
@@ -9,7 +9,7 @@ service CatalogService {
9
9
  }
10
10
 
11
11
  annotate CatalogService.Books with @UI.LineItem: [
12
- { Value: ID },
13
- { Value: title },
14
- { Value: stock },
12
+ {Value: ID},
13
+ {Value: title},
14
+ {Value: stock},
15
15
  ];
@@ -28,7 +28,7 @@ module.exports = class ExtensionProjectTemplate extends require('../../plugin')
28
28
  if (typeof base !== 'string' || !base?.length) base = '@capire/bookshop'
29
29
 
30
30
  const pkgJson = exists('package.json') ? await read('package.json') : { name: 'my-extension-project' }
31
- if (!pkgJson.extends || force) {
31
+ if (force || !pkgJson.extends) {
32
32
  pkgJson.extends = base
33
33
  }
34
34
 
@@ -38,7 +38,7 @@ module.exports = class ExtensionProjectTemplate extends require('../../plugin')
38
38
  }
39
39
  await write('package.json', pkgJson, { spaces: 2 })
40
40
 
41
- if (!exists('.base/package.json') || force) {
41
+ if (force || !exists('.base/package.json')) {
42
42
  await write('.base/package.json', {name: base})
43
43
  }
44
44
 
@@ -56,17 +56,17 @@ module.exports = class ExtensionProjectTemplate extends require('../../plugin')
56
56
  const extensionNamespace = `${namespacify(proj.appName)}.${namespacify(projectSimple)}`
57
57
  const { force } = cds.cli.options
58
58
 
59
- if (!exists('.base/index.cds') || force) {
59
+ if (force || !exists('.base/index.cds')) {
60
60
  await copy(join(__dirname, 'files/.base/index.cds'), '.base/index.cds')
61
61
  }
62
62
 
63
- if (!exists('app/extension.cds') || force) {
63
+ if (force || !exists('app/extension.cds')) {
64
64
  await renderAndCopy(join(__dirname, 'files/app'), 'app', {
65
65
  basePackage: base,
66
66
  extensionNamespace
67
67
  })
68
68
  }
69
- if (!exists('test/data') || force) {
69
+ if (force || !exists('test/data')) {
70
70
  await copy(join(__dirname, 'files/test/data/my.bookshop-Books.csv'), 'test/data/my.bookshop-Books.csv')
71
71
  await copy(join(__dirname, 'files/test/data/Authors.csv'), `test/data/${extensionNamespace}-x_Authors.csv`)
72
72
  }