@sap/cds-dk 9.0.4 → 9.0.6

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 CHANGED
@@ -6,6 +6,24 @@ 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.0.6 - 2025-06-18
10
+
11
+ ### Changed
12
+
13
+ - `cds build` now adds an `engines.node = ">=20"` entry to the effective _package.json_ iff it is missing from the project's _package.json_ to avoid engine confusion when deploying to Cloud Foundry
14
+
15
+ ## Version 9.0.5 - 2025-06-06
16
+
17
+ ### Changed
18
+
19
+ - `cds init` uses latest Maven Java archetype version 4.0.2 for creating Java projects.
20
+
21
+ ### Fixed
22
+
23
+ - `cds build` for extensions now filters built-in entities such as `cds.outbox.Messages` to fix the extension upload with `cds push`.
24
+ - `cds add data` now correctly works for nested structured properties.
25
+ - `cds add data/http` no longer create decimal numbers with too many precision places
26
+
9
27
  ## Version 9.0.4 - 2025-05-28
10
28
 
11
29
  ### Changed
@@ -95,6 +113,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
95
113
  - `cds add multitenancy` requires the `srv-api` instead of the `mtx-api` for Java projects.
96
114
 
97
115
 
116
+ ## Version 8.9.5 - 2025-06-05
117
+
118
+ ### Fixed
119
+
120
+ - Bump `tar-fs` to address CVE-2024-12905.
121
+ - `cds add data` now correctly works for nested structured properties.
122
+ - Help text of `cds debug`.
123
+
98
124
  ## Version 8.9.4 - 2025-05-26
99
125
 
100
126
  ### Changed
package/bin/cds.js CHANGED
@@ -106,7 +106,7 @@ const cli = {
106
106
  },
107
107
 
108
108
  load (cmd,_default) {
109
- return /[\\/.-@]/.test(cmd) && _default && _default(true) // like `cds ./srv` or `cds @capire/bookshop`
109
+ return /[\\/.\-@]/.test(cmd) && _default && _default(true) // like `cds ./srv` or `cds @capire/bookshop`
110
110
  || /cds/i.test(cmd) && _dieForUnknownCommand (cmd) // cds cds
111
111
  || _require ('./'+cmd) // cds-dk commands
112
112
  || _default && _default()
package/bin/debug.js CHANGED
@@ -15,7 +15,7 @@ module.exports = Object.assign(debug, {
15
15
  Cloud Foundry space (check with 'cf target').
16
16
  SSH access to the app is required (check with 'cf ssh-enabled').
17
17
 
18
- If no <app> is given, the app in the current working is started
18
+ If no <app> is given, the app in the current working directory is started
19
19
  (with 'cds watch --debug' for Node.js and 'mvn spring-boot:run' for Java).
20
20
 
21
21
  # OPTIONS
package/bin/help.js CHANGED
@@ -13,7 +13,6 @@ module.exports = exports = Object.assign (help, {formatHelp, handleCompletion, h
13
13
  *i* | *init* jump-start cds-based projects
14
14
  *a* | *add* add a feature to an existing project
15
15
  * * | *gen* generate models/data using a descriptive prompt [beta]
16
- *y* | *bind* bind application to remote services
17
16
  *m* | *import* add models from external sources
18
17
  *c* | *compile* compile cds models to different outputs
19
18
  *p* | *parse* parses given cds models
@@ -23,8 +22,9 @@ module.exports = exports = Object.assign (help, {formatHelp, handleCompletion, h
23
22
  *r* | *repl* read-eval-event loop
24
23
  *e* | *env* inspect effective configuration
25
24
  *b* | *build* prepare for deployment
26
- *d* | *deploy* deploy to databases or cloud
27
- * * | *up* build and deploy your application
25
+ *d* | *deploy* deploy to databases
26
+ * * | *up* build and deploy your application to the cloud
27
+ *y* | *bind* bind application to remote services
28
28
  * * | *debug* debug your application
29
29
  * * | *subscribe* subscribe a tenant to a multitenant SaaS app
30
30
  * * | *unsubscribe* unsubscribe a tenant from a multitenant SaaS app
@@ -1,7 +1,7 @@
1
1
  const path = require('path')
2
2
  const cds = require('../../../cds')
3
3
  const EdmxBuildPlugin = require('../edmxBuildPlugin')
4
- const URL = require('url')
4
+ const { URL } = require('node:url')
5
5
  const { getProperty, relativePaths } = require('../../util')
6
6
  const { OUTPUT_MODE, OUTPUT_MODE_RESULT, BUILD_TASK_FIORI, CDS_CONFIG_PATH_SEP, OUTPUT_MODE_FILESYSTEM } = require('../../constants')
7
7
  const DEBUG = cds.debug('cli|build')
@@ -52,7 +52,7 @@ class FioriBuildPlugin extends EdmxBuildPlugin {
52
52
  appModelGroups.set(appFolder, appModels)
53
53
  }
54
54
 
55
- // ensure edmx is cached as build result
55
+ // ensure edmx is cached as build result
56
56
  const buildOption = this.context.options[OUTPUT_MODE]
57
57
  this.context.options[OUTPUT_MODE] |= OUTPUT_MODE_RESULT
58
58
 
@@ -159,8 +159,8 @@ class FioriBuildPlugin extends EdmxBuildPlugin {
159
159
  }
160
160
 
161
161
  _strippedUrlPath(urlString) {
162
- const url = URL.parse(urlString)
163
- return url.pathname.replace(/^(\/|\\)/, '').replace(/(\/|\\)$/, '') // strip leading and trailing slash or backslash)
162
+ const url = new URL(urlString, 'http://dummy.base') // need dummy base to parse relative URLs correctly
163
+ return url.pathname.replace(/^(\/|\\)/, '').replace(/(\/|\\)$/, '') // strip leading and trailing slash or backslash
164
164
  }
165
165
  }
166
166
 
@@ -8,6 +8,8 @@ const { FOLDER_GEN, EXTENSION_POINT_VALIDATION } = require('../../constants')
8
8
  const { BuildError, BuildMessage } = require('../../util')
9
9
  const DEBUG = cds.debug('cli|build')
10
10
 
11
+ const BUILT_IN_NAMESPACES = ['cds.core', 'cds.outbox', 'cds.xt']
12
+
11
13
  class MtxExtensionBuildPlugin extends InternalBuildPlugin {
12
14
  init() {
13
15
  super.init()
@@ -48,6 +50,16 @@ class MtxExtensionBuildPlugin extends InternalBuildPlugin {
48
50
  if (extensionModel.requires) {
49
51
  extensionModel.requires.length = 0
50
52
  }
53
+ if (extensionModel.definitions) {
54
+ for (const [name] of Object.entries(extensionModel.definitions)) {
55
+ for (const namespace of BUILT_IN_NAMESPACES) {
56
+ if (name.startsWith(`${namespace}.`)) {
57
+ delete extensionModel.definitions[name]
58
+ break
59
+ }
60
+ }
61
+ }
62
+ }
51
63
  }
52
64
  }
53
65
 
@@ -9,6 +9,7 @@ const { OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM, ODATA_VERSION_V2, FOLDER_GEN, CONTE
9
9
  OPTION_WS_PACK} = require('../../constants')
10
10
  const { WARNING } = EdmxBuildPlugin
11
11
  const LOG = cds.log('cli|build'), DEBUG = cds.debug('cli|build')
12
+ const NODE_ENGINE_VERSION = '>=20'
12
13
 
13
14
  /**
14
15
  * @param {T | T[]} x
@@ -62,6 +63,23 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
62
63
  if (this.isStagingBuild() && this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM)) {
63
64
  const srcSrv = this.task.src === cds.root ? path.resolve(this.task.src, cds.env.folders.srv) : this.task.src
64
65
  await this._copyNativeContent(cds.root, srcSrv, destRoot, destSrv)
66
+ try {
67
+ // add minimal node engine to effective package.json.
68
+ // CloudFoundry uses this entry to choose the node engine in buildpack and may
69
+ // fall back to an outdated version if this entry is missing.
70
+ const packageJsonPath = path.join(destRoot, 'package.json')
71
+ const packageJsonContent = require(packageJsonPath)
72
+ if (!Object.hasOwn(packageJsonContent, 'engines') || !Object.hasOwn(packageJsonContent.engines, 'node')) {
73
+ DEBUG?.(`Amending package.json with node engine version ${NODE_ENGINE_VERSION}`)
74
+ packageJsonContent.engines = packageJsonContent.engines ?? {}
75
+ packageJsonContent.engines.node = NODE_ENGINE_VERSION
76
+ await this.write(JSON.stringify(packageJsonContent, 0, 2)).to(packageJsonPath)
77
+ }
78
+ } catch (e) {
79
+ DEBUG?.(e)
80
+ // silently ignore package json not being found
81
+ if (e.code !== 'MODULE_NOT_FOUND') throw e
82
+ }
65
83
  if (this.context.options[OPTION_WS_PACK]) {
66
84
  await this._packWorkspaceDependencies(destRoot)
67
85
  }
@@ -5,7 +5,7 @@ module.exports = {
5
5
  COMMAND_ADD: 'add',
6
6
 
7
7
  JAVA_LTS_VERSIONS: [17, 21],
8
- MAVEN_ARCHETYPE_VERSION: '3.10.1',
8
+ MAVEN_ARCHETYPE_VERSION: '4.0.2',
9
9
 
10
10
  OPTIONS: Object.freeze({
11
11
  COMPLETION: 'completion',
@@ -40,10 +40,11 @@ module.exports = async (data={}, csn, { headerOnly=false, separator=',' }={}) =>
40
40
  record.push(...Object.values(value))
41
41
  }
42
42
  else if (element.is_struct && element.type !== 'cds.Map') {
43
+ const {headers: structHeaders, records: structRecords} = resolveStruct(value, name);
43
44
  if (i === 0) {
44
- header.push(...Object.keys(value).map(structField => name + '_' + structField))
45
+ header.push(...structHeaders)
45
46
  }
46
- record.push(...Object.values(value))
47
+ record.push(...structRecords)
47
48
  }
48
49
  else {
49
50
  if (i === 0) header.push(name)
@@ -62,13 +63,28 @@ module.exports = async (data={}, csn, { headerOnly=false, separator=',' }={}) =>
62
63
 
63
64
  data[entity.name] = res
64
65
 
65
- function escape(value) {
66
- if (typeof value === 'string' && (value.includes('"') || value.includes(separator))) {
67
- return `"${value.replace(/"/g, '""')}"` // escape double quotes, then quote the value
68
- }
69
- return value
70
- }
71
66
  })
67
+ function escape(value) {
68
+ if (typeof value === 'string' && (value.includes('"') || value.includes(separator))) {
69
+ return `"${value.replace(/"/g, '""')}"` // escape double quotes, then quote the value
70
+ }
71
+ return value
72
+ }
73
+
74
+ function resolveStruct(v, prefix) {
75
+ return Object.entries(v).reduce((acc, [key, val]) => {
76
+ //structs can be within structs
77
+ if (typeof val === 'object') {
78
+ const {headers, records} = resolveStruct(val, prefix + '_' + key);
79
+ acc.headers = acc.headers.concat(headers);
80
+ acc.records = acc.records.concat(records);
81
+ } else {
82
+ acc.headers.push(prefix + '_' + key);
83
+ acc.records.push(val);
84
+ }
85
+ return acc;
86
+ }, {headers: [], records: []});
87
+ }
72
88
  }
73
89
  return data
74
90
  }
@@ -228,10 +228,10 @@ function dataForField(element, artifact, csn, record, data, parent, depth, refer
228
228
  return randomIntInRange(range)
229
229
  }
230
230
  case 'cds.hana.REAL':
231
- case 'cds.Decimal': {
232
- const prec = element.precision ? Math.min(Math.pow(10, element.precision), Number.MAX_SAFE_INTEGER) : 100
233
- const range = { from: 0, to: prec }
231
+ case 'cds.Decimal': { // Decimal(9,5), Decimal(9), Decimal(precision:5)
234
232
  const scale = element.scale ?? (element.precision ? 0 : 2) // scale is 0 if only precision is given
233
+ const prec = element.precision ? Math.min(Math.pow(10, element.precision-scale), Number.MAX_SAFE_INTEGER) : 100
234
+ const range = { from: 0, to: prec }
235
235
  annotatedIntRange(element, range)
236
236
  return +(randomDecimalInRange(range).toFixed(scale))
237
237
  }
@@ -13,8 +13,8 @@ module.exports = class HanaTemplate extends require('../../plugin') {
13
13
  return 'database support for SAP HANA'
14
14
  }
15
15
 
16
- static hasInProduction(env) {
17
- return env.requires?.db?.kind === 'hana' || readProject().dependencies?.['@cap-js/hana']
16
+ static hasInProduction() {
17
+ return readProject().dependencies?.['@cap-js/hana']
18
18
  }
19
19
 
20
20
  async run() {
@@ -145,7 +145,7 @@ async function createExtCodeHandler(service, proj, out, force) {
145
145
  // package.json
146
146
  const pkgJson = await read('package.json')
147
147
  const pkgJsonDelta = require('./files/extension/package.json')
148
- deepMerge(pkgJson, pkgJsonDelta)
148
+ cds.utils.merge ? cds.utils.merge(pkgJson, pkgJsonDelta) : merge(pkgJson, pkgJsonDelta)
149
149
  await write('package.json', pkgJson, { spaces: 2 })
150
150
  }
151
151
 
@@ -161,15 +161,19 @@ async function loadModel() {
161
161
  }
162
162
  }
163
163
 
164
- function deepMerge(obj1, obj2) {
165
- for (const key in obj2) {
166
- if (Object.prototype.hasOwnProperty.call(obj2, key)) {
167
- if (obj2[key] instanceof Object && obj1[key] instanceof Object) {
168
- obj1[key] = deepMerge(obj1[key], obj2[key])
169
- } else {
170
- obj1[key] = obj2[key]
171
- }
172
- }
173
- }
174
- return obj1
164
+ // TODO remove w/ cds 10, copied from cds-utils.js
165
+ /**
166
+ * Polyfill for cds.utils.merge
167
+ * Simple helper to deep-merge two or more objects.
168
+ * Entries from `xs` overwrite entries in `o`.
169
+ * @example cds.utils.merge({foo:1},{bar:2},{baz:3})
170
+ * @returns `o` with entries from `xs` merged in.
171
+ */
172
+ function merge (o,...xs) {
173
+ let v; for (let x of xs) for (let k in x)
174
+ if (k === '__proto__' || k === 'constructor') continue //> avoid prototype pollution
175
+ else o[k] = is_object(v=x[k]) ? merge(o[k]??={},v) : v
176
+ return o
175
177
  }
178
+ const is_object = x => typeof x === 'object' && x !== null && !is_array(x)
179
+ const is_array = Array.isArray
@@ -11,7 +11,7 @@
11
11
  "@cap-js/sqlite": "^2"
12
12
  },
13
13
  "engines": {
14
- "node": "^22"
14
+ "node": ">=20"
15
15
  },
16
16
  "scripts": {
17
17
  "start": "cds-serve"{{#isJava}},
@@ -10,11 +10,11 @@
10
10
  "express": "^4"
11
11
  },
12
12
  "engines": {
13
- "node": "^22"
13
+ "node": ">=20"
14
14
  },
15
15
  "devDependencies": {
16
16
  "@cap-js/sqlite": "^2",
17
- "@cap-js/cds-types": "^0.10.0"
17
+ "@cap-js/cds-types": "^0.11.0"
18
18
  },
19
19
  "scripts": {
20
20
  "start": "cds-serve"
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "dependencies": {
3
- "@cap-js/postgres": "^1"
3
+ "@cap-js/postgres": "^2"
4
4
  }
5
5
  }
@@ -7,6 +7,12 @@ using { sap.capire.bookshop } from '../../db/schema';
7
7
  //
8
8
 
9
9
  annotate AdminService.Books with @(UI: {
10
+ HeaderInfo : {
11
+ TypeName : '{i18n>Book}',
12
+ TypeNamePlural: '{i18n>Books}',
13
+ Title : {Value: title},
14
+ Description : {Value: author.name}
15
+ },
10
16
  Facets: [
11
17
  {
12
18
  $Type : 'UI.ReferenceFacet',
@@ -110,6 +110,7 @@
110
110
  "options": {
111
111
  "settings": {
112
112
  "entitySet": "Books",
113
+ "editableHeaderContent": false,
113
114
  "navigation": {
114
115
  "Authors": {
115
116
  "detail": {
@@ -6,8 +6,9 @@ using { CatalogService } from '../../srv/cat-service.cds';
6
6
  //
7
7
  annotate CatalogService.Books with @(UI : {
8
8
  HeaderInfo: {
9
- TypeName : 'Book',
10
- TypeNamePlural: 'Books',
9
+ TypeName : '{i18n>Book}',
10
+ TypeNamePlural: '{i18n>Books}',
11
+ Title : {Value: title},
11
12
  Description : {Value : author}
12
13
  },
13
14
  HeaderFacets: [{
@@ -41,17 +41,6 @@ annotate Currencies with {
41
41
  symbol @Common.Label: '{i18n>Currency}';
42
42
  }
43
43
 
44
- ////////////////////////////////////////////////////////////////////////////
45
- //
46
- // Books Details
47
- //
48
- annotate my.Books with @(UI : {HeaderInfo : {
49
- TypeName : '{i18n>Book}',
50
- TypeNamePlural: '{i18n>Books}',
51
- Title : { Value: title },
52
- Description : { Value: author.name }
53
- }, });
54
-
55
44
 
56
45
  ////////////////////////////////////////////////////////////////////////////
57
46
  //
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "devDependencies": {
3
- "@cap-js/cds-types": "^0.10.0",
3
+ "@cap-js/cds-types": "^0.11.0",
4
4
  "@types/node": "^22.0.0",
5
5
  "tsx": "^4",
6
6
  "typescript": "^5"