@sap/cds-dk 8.0.2 → 8.1.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 (53) hide show
  1. package/CHANGELOG.md +49 -3
  2. package/bin/bind.js +3 -3
  3. package/bin/cds.js +8 -7
  4. package/bin/init.js +3 -6
  5. package/bin/version.js +5 -3
  6. package/bin/watch.js +12 -4
  7. package/lib/build/constants.js +1 -0
  8. package/lib/build/provider/hana/index.js +54 -37
  9. package/lib/build/provider/internalBuildTaskProvider.js +2 -2
  10. package/lib/build/provider/mtx-sidecar/index.js +10 -3
  11. package/lib/build/provider/nodejs/index.js +4 -3
  12. package/lib/compile/to-mermaid.js +27 -12
  13. package/lib/deploy/to-hana/hana.js +10 -8
  14. package/lib/deploy/to-hana/hdiDeployUtil.js +9 -1
  15. package/lib/init/constants.js +13 -3
  16. package/lib/init/index.js +6 -4
  17. package/lib/init/projectReader.js +3 -4
  18. package/lib/init/template/audit-logging/files/package.sidecar.json +7 -0
  19. package/lib/init/template/audit-logging/index.js +1 -1
  20. package/lib/init/template/cf-manifest/index.js +4 -0
  21. package/lib/init/template/cov2ap/files/dependencies.package.json +5 -0
  22. package/lib/init/template/cov2ap/index.js +31 -0
  23. package/lib/init/template/data/as-json.js +25 -11
  24. package/lib/init/template/destination/files/mta.yaml.hbs +6 -0
  25. package/lib/init/template/destination/files/package.sidecar.json +7 -0
  26. package/lib/init/template/destination/index.js +12 -2
  27. package/lib/init/template/event-queue/files/dependencies.package.json +5 -0
  28. package/lib/init/template/event-queue/index.js +35 -0
  29. package/lib/init/template/html5-repo/files/mta.yaml.hbs +6 -58
  30. package/lib/init/template/html5-repo/files/package.sidecar.json +7 -0
  31. package/lib/init/template/html5-repo/files/xs-app.json.hbs +1 -15
  32. package/lib/init/template/html5-repo/index.js +13 -10
  33. package/lib/init/template/http/index.js +37 -28
  34. package/lib/init/template/malware-scanner/index.js +1 -1
  35. package/lib/init/template/mta/files/package.json +1 -1
  36. package/lib/init/template/redis/files/mta.yaml.hbs +25 -0
  37. package/lib/init/template/redis/files/package.json.hbs +5 -0
  38. package/lib/init/template/redis/index.js +44 -0
  39. package/lib/init/template/redis-messaging/files/package.json.hbs +0 -3
  40. package/lib/init/template/redis-messaging/index.js +4 -18
  41. package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +0 -1
  42. package/lib/init/template/sample/files/app/browse/webapp/manifest.json +0 -1
  43. package/lib/init/template/sample/files/nodejs/admin-service.js +13 -8
  44. package/lib/init/template/sample/files/nodejs/cat-service.js +27 -15
  45. package/lib/init/template/typescript/files/package.json +3 -3
  46. package/lib/init/template/websocket/files/dependencies.package.json +5 -0
  47. package/lib/init/template/websocket/index.js +35 -0
  48. package/lib/init/template/workzone/index.js +1 -0
  49. package/lib/init/template/workzone-standard/files/mta.yaml.hbs +59 -0
  50. package/lib/init/template/workzone-standard/index.js +24 -8
  51. package/npm-shrinkwrap.json +22 -19
  52. package/package.json +1 -1
  53. package/lib/init/template/redis-messaging/files/mta.yaml.hbs +0 -14
package/CHANGELOG.md CHANGED
@@ -7,6 +7,50 @@ This project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
8
  The format is based on [Keep a Changelog](http://keepachangelog.com/).
9
9
 
10
+ ## Version 8.1.0 - 2024-07-26
11
+
12
+ ### Added
13
+
14
+ - `cds compile --to mermaid` can be configured with a layout direction like `LR`.
15
+ - `cds deploy --to hana` now shows the `hdi-deploy` version when running.
16
+
17
+ ### Changed
18
+
19
+ - Better generation of http files with `cds add http`, especially for draft requests.
20
+
21
+ ### Fixed
22
+
23
+ - `cds add` recognizes `hasCfManifest` as inactive unless a `manifest.yml` file exists.
24
+ - `cds add` help and suggestions are only shown when the `help` method is implemented.
25
+ - `cds add sample` does not include the deprecated `synchronizationMode` property in `manifest.json` files any more.
26
+ - `cds compile --to mermaid` no longer produces empty `namespace` blocks that would lead to rendering errors.
27
+ - `cds compile --to mermaid` no longer fails for complex queries like joins.
28
+ - `cds build --for mtx-sidecar` now correctly supports different `srv` folder name.
29
+ - `cds add audit-log` in combination with `cds add multitenancy` correctly adds the audit-log dependency to the MTX sidecar.
30
+ - `cds build` adds the correct hdbtabledata file paths to the hana result set.
31
+ - `cds add` plugins can now correctly interpret `cds.cli` properties.
32
+ - `cds deploy --to hana --dry` now shows correct files with their content.
33
+ - `cds version` now reports the correct version of `@sap/cds` if this one is installed locally along with a local `@sap/cds-dk` installation.
34
+ - Shell completion now works correctly for `cds init --add`.
35
+ - `cds build --for mtx-sidecar` no longer fails if the severity of a compilation error has been downgraded from `Error` to `Warning`.
36
+
37
+ ## Version 8.0.3 - 2024-07-20
38
+
39
+ ### Fixed
40
+
41
+ - `cds build --ws` no longer creates migration tables in shared db if already existing in a workspace.
42
+ - `cds add destination` adds the destination service as an MTX SaaS dependency.
43
+ - `cds add html5-repo` adds the HTML5 repo runtime as an MTX SaaS dependency.
44
+ - `cds add html5-repo` does not add a destination service any more if not required by other services.
45
+ - `cds add workzone` runs `cds add destination` as a dependent facet.
46
+ - `cds add` plugins don't fail if custom options are provided via the `options` API.
47
+ - `cds add data` can now correctly handle circular associations. The maximum depth of associations is increased to 5.
48
+ - `cds add redis` is separated from `cds add redis-messaging` to allow for separate consumption.
49
+ - `cds add redis` uses the `standard` service plan by default.
50
+ - `cds watch` no longer leads to a warning `'@sap/cds' was loaded from different installations` when local project dependencies are installed.
51
+ - `cds init` uses latest Maven Java archetype version 3.0.0 for creating Java projects.
52
+
53
+
10
54
  ## Version 8.0.2 - 2024-07-12
11
55
 
12
56
  ### Added
@@ -20,9 +64,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
20
64
  - `cds init` no longer creates a `.cdsrc.json` for Node.js projects.
21
65
  - `cds build` configuration cleanup for `data.model` and `service.model` to only support configuration of a single model folder.
22
66
 
23
- ### Fixed
24
-
25
-
26
67
  ## Version 8.0.1 - 2024-07-09
27
68
 
28
69
  ### Added
@@ -128,6 +169,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
128
169
 
129
170
  - `cds add notebook` is removed. CAP Jupyter Notebooks have been replaced by custom CAP Notebooks for VS Code which are now part of the CDS Editor. See https://cap.cloud.sap/docs/tools/#add-cds-editor for more.
130
171
 
172
+ ## Version 7.9.6 - 2024-07-19
173
+
174
+ ### Fixed
175
+
176
+ - `cds add data` can now correctly handle circular associations. The maximum depth of associations is increased to 5.
131
177
 
132
178
  ## Version 7.9.5 - 2024-07-12
133
179
 
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 to a given application (Cloud Foundry only).
49
+ Bind services of a given application (Cloud Foundry only).
50
50
 
51
51
  *--no-create-service-key*
52
52
 
@@ -164,11 +164,11 @@ async function cds_bind(args, options) {
164
164
  options.targets = options.to.split(/,/g);
165
165
 
166
166
  if (options.targets.length >= 2 && options.serviceArg) {
167
- throw `Service argument cannot be specified together with multiple targets (${term.bold('--to')}) services. Use one service per call or omit the service argument.`;
167
+ throw `Service argument cannot be specified together with multiple target (${term.bold('--to')}) services. Use one service per call or omit the service argument.`;
168
168
  }
169
169
 
170
170
  if (options.targets.length >= 2 && options.kind) {
171
- throw `The option '--kind' cannot be specified together with multiple targets (${term.bold('--to')}) services. Use one service per call or omit the ${term.bold('--kind')} option.`;
171
+ throw `The option '--kind' cannot be specified together with multiple target (${term.bold('--to')}) services. Use one service per call or omit the ${term.bold('--kind')} option.`;
172
172
  }
173
173
 
174
174
  const services = await bind({ ...options, out });
package/bin/cds.js CHANGED
@@ -55,6 +55,14 @@ const cli = {
55
55
  argv = argv.slice(0, endOfOptions)
56
56
  }
57
57
 
58
+ // REVISIT: Have to load plugins to allow for plugin-contributed flags!
59
+ if (cmd === 'add') {
60
+ cds.cli = { command: 'add' }
61
+ await cds.plugins
62
+ for (const [, Plugin] of cds.add.registered) {
63
+ task.options.push(...Object.keys((new Plugin).options()).map(key => '--'+key))
64
+ }
65
+ }
58
66
  const args = this.args(task, argv)
59
67
  args[0].push(...appendArgs)
60
68
  cds.cli = {
@@ -63,13 +71,6 @@ const cli = {
63
71
  options: args[1],
64
72
  }
65
73
 
66
- // REVISIT: Have to do it here to allow custom flags for plugins!
67
- if (cmd === 'add') {
68
- await cds.plugins
69
- for (const [, Plugin] of cds.add.registered) {
70
- task.options.push(...Object.keys((new Plugin).options()).map(key => '--'+key))
71
- }
72
- }
73
74
  if (args[1]?.['resolve-bindings']) await _resolveBindings({ silent: cmd === 'env' })
74
75
 
75
76
  // cds.root may for some subcommands not point to the correct directory (e.g. cds w where users can pass a project directory)
package/bin/init.js CHANGED
@@ -61,18 +61,15 @@ async function handleCompletion(currentWord, previousWord, argv, util) {
61
61
  return allOptionsFlags;
62
62
  }
63
63
 
64
- if (previousWord === '--add') {
65
- return util.completionFs.readTemplates(argv);
66
- }
67
-
68
64
  if (previousWord === '--java:package') {
69
65
  const excludes = ['_merging'];
70
66
  return (await util.completionFs.readdir(currentWord, { files: false }))
71
67
  .filter(e => !excludes.includes(e));
72
68
  }
73
69
 
74
- if (!currentWord && init.options?.includes(previousWord)) {
75
- return [];
70
+ const templates = await util.completionFs.readTemplates(argv);
71
+ if (previousWord === '--add' || argv.includes('--add') && previousWord.endsWith(',')) {
72
+ return templates;
76
73
  }
77
74
 
78
75
  return allOptionsFlags;
package/bin/version.js CHANGED
@@ -61,7 +61,8 @@ function list_versions(args, options) {
61
61
  }
62
62
 
63
63
  function info({ root:cwd=process.cwd() }) {
64
- const env = require('../lib').env.for('cds', cwd)
64
+ const cds = require('../lib')
65
+ const env = cds.env.for('cds', cwd)
65
66
  env.cli ??= {}; env.cli.version ??= {}
66
67
  const javaInfo = !env.cli.version.skipjava && env['project-nature'] === 'java'
67
68
  if (javaInfo && process.stderr.isTTY) {
@@ -74,10 +75,11 @@ function info({ root:cwd=process.cwd() }) {
74
75
  ..._versions_from(_pkg_path('@sap/cds-dk', npmRoot), '@sap/cds-dk (global)'), // ... or installed globally
75
76
  ..._versions_from(_pkg_path('@sap/cds-dk', cwd)), // local dk, important for Java projects
76
77
  ..._versions_from(_pkg_path('@sap/cds', __dirname)), // @sap/cds from here
77
- ..._versions_from(cwd), // ... or from local app dependencies
78
+ ..._versions_from(_pkg_path('@sap/cds', cds.home)), // ... or local @sap/cds
79
+ ..._versions_from(cwd), // ... more from local app dependencies
78
80
  ..._versions_from(_pkg_path('@sap/cds-compiler', cwd, __dirname)), // compiler is no direct dependency, so list it explicitly
79
81
  'Node.js': process.version,
80
- 'home': require('../lib/cds').home, // effective (local) cds
82
+ 'home': cds.home, // effective cds home
81
83
  }
82
84
  // sort Node entries
83
85
  versions = Object.keys(versions).sort().reduce((all, key) => { all[key] = versions[key]; return all }, {})
package/bin/watch.js CHANGED
@@ -76,6 +76,7 @@ async function watch ([cwd], {
76
76
  } catch (err) {
77
77
  throw `Error: No such folder or package: '${cwd}'`
78
78
  }
79
+ const _cwd = cwd || process.cwd()
79
80
 
80
81
  if (!Object.keys(options).includes('with-mocks')) args.push ('--with-mocks')
81
82
  if (!Object.keys(options).find(a => /^in-memory\??$/.test(a))) args.push ('--in-memory?')
@@ -91,18 +92,20 @@ async function watch ([cwd], {
91
92
  })
92
93
  if (process.env.CDS_TYPESCRIPT) {
93
94
  // Needed by ts-node/tsc to find the tsconfig.json. Same as `--project`.
94
- env.TS_NODE_PROJECT = env.TS_NODE_PROJECT ?? cwd ?? process.cwd()
95
+ env.TS_NODE_PROJECT = env.TS_NODE_PROJECT ?? _cwd
95
96
  }
96
97
 
97
- // allow running w/o or partial local node_modules, also add cds-dk for its sqlite
98
- env.NODE_PATH = [...paths(cwd||process.cwd()), ...paths(__dirname)].join(delimiter)
98
+ // if sap/cds is not installed locally, run with cds-dk's deps. (sap/cds, sqlite etc)
99
+ if (!installed('@sap/cds', _cwd)) {
100
+ env.NODE_PATH = [...paths(_cwd), ...paths(__dirname)].join(delimiter)
101
+ }
99
102
 
100
103
  if (typeof ext !== 'string') ext = extDefaults
101
104
  const delay = parseInt(process.env.CDS_WATCH_DELAY) || 200
102
105
 
103
106
  let liveReload //= ...
104
107
  let firstTime = true
105
- if (!/false|no/.test(options['livereload']) && cds.env.for('cds', cwd||process.cwd()).livereload !== false) {
108
+ if (!/false|no/.test(options['livereload']) && cds.env.for('cds', _cwd).livereload !== false) {
106
109
  liveReload = new (require ('../lib/watch/livereload-server'))
107
110
  env.CDS_LIVERELOAD_PATH = require.resolve ('../lib/watch/livereload-connect')
108
111
  env.CDS_LIVERELOAD_URL = await liveReload.start (options['livereload'], cwd, delay)
@@ -199,3 +202,8 @@ function paths (dir) {
199
202
  const parts = resolve(dir).split(sep), n = parts.length, nm = sep+'node_modules'
200
203
  return parts.map ((_,i,a)=> a.slice(0,n-i).join(sep)+nm).filter(p => fs.existsSync(p))
201
204
  }
205
+
206
+ function installed(id, ...paths) {
207
+ try { return require.resolve (id, { paths }) }
208
+ catch(e) { if (e.code !== 'MODULE_NOT_FOUND') throw e }
209
+ }
@@ -35,6 +35,7 @@ exports.CONTENT_ENV = "contentEnv" // copy .env fil
35
35
  exports.CONTENT_DEFAULT_ENV_JSON = "contentDefaultEnvJson" // copy default-env.json file into deployment folder - DISABLED by default
36
36
 
37
37
  exports.OPTION_WS = "ws" // run a workspace build, e.g. including model folders of configured npm workspaces
38
+ exports.OPTION_WS_PACK = "ws-pack" // enable tarball based packaging of workspace dependencies
38
39
  exports.CSV_FILE_TARGET = "csvFileTarget" // target folder when copying CSV files to the deployment target folder ./db/src/gen/*, default is 'data'
39
40
  exports.CSV_FILE_DETECTION = "csvFileDetection" // detect CSV files based on CDS model location - ENABLED by default
40
41
  exports.EXTENSION_POINT_VALIDATION = "extensionPointValidation" // validate extension point restrictions defined by the extended SaaS app - ENABLED by default
@@ -6,7 +6,7 @@ const InternalBuildPlugin = require('../internalBuildPlugin')
6
6
  const { BuildError, relativePaths, BuildMessage, getWorkspaces, hasOptionValue, getFiles } = require('../../util')
7
7
  const { OUTPUT_MODE_NONE, OUTPUT_MODE, CONTENT_PACKAGE_JSON, CONTENT_HDBTABLEDATA, CSV_FILE_DETECTION,
8
8
  CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, CONTENT_NODE_MODULES, OUTPUT_MODE_RESULT, CONTINUE_UNRESOLVED_SCHEMA_CHANGES,
9
- CSV_FILE_TARGET, OPTION_WS} = require('../../constants')
9
+ CSV_FILE_TARGET, OPTION_WS } = require('../../constants')
10
10
  const { WARNING } = InternalBuildPlugin
11
11
  const DEFAULT_COMPILE_DEST_FOLDER = path.normalize("src/gen")
12
12
  const FILE_EXT_CSV = ".csv"
@@ -26,7 +26,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
26
26
  if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
27
27
  this._result = {
28
28
  dest: this.task.dest,
29
- hana: []
29
+ hana: new Set()
30
30
  }
31
31
  }
32
32
  this.task.options.compileDest = path.resolve(this.task.dest, this.task.options.compileDest || DEFAULT_COMPILE_DEST_FOLDER)
@@ -38,9 +38,14 @@ class HanaBuildPlugin extends InternalBuildPlugin {
38
38
  if (!model) {
39
39
  return
40
40
  }
41
+ let workspaceFiles
42
+ if (cds.cli.options?.[OPTION_WS] || hasOptionValue(this.task.options[OPTION_WS], true)) {
43
+ workspaceFiles = await this._getWorkspaceFiles()
44
+ }
45
+
41
46
  // the order of 1 and 2 is important
42
47
  // 1. compile
43
- const hdiPlugins = await this._compileToHana(model)
48
+ const hdiPlugins = await this._compileToHana(model, workspaceFiles)
44
49
 
45
50
  if (!this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_NONE)) {
46
51
  // 2. copy resources
@@ -49,9 +54,15 @@ class HanaBuildPlugin extends InternalBuildPlugin {
49
54
  } else {
50
55
  await this._copyResourcesExt(src, dest, model)
51
56
  }
52
- // copy native hana artifacts from workspace dependencies
53
- if (cds.cli.options?.[OPTION_WS] || hasOptionValue(this.task.options?.[OPTION_WS], true)) {
54
- await this._addWorkspaceContent()
57
+ // copy native hana artifacts from workspaces
58
+ if (workspaceFiles) {
59
+ const dest = path.join(this.task.dest, 'src/gen')
60
+ await Promise.all(
61
+ workspaceFiles.map((src) => {
62
+ const { dir, filePath } = src
63
+ return this.copy(path.join(dir, filePath)).to(path.join(dest, filePath))
64
+ })
65
+ )
55
66
  }
56
67
 
57
68
  // create additional stuff in dest
@@ -217,7 +228,6 @@ class HanaBuildPlugin extends InternalBuildPlugin {
217
228
  }
218
229
 
219
230
  const promises = []
220
- const relDest = path.relative(this.task.dest, this.task.options.compileDest)
221
231
  const options = { ...this.options(), messages: this.messages, dirs: csvDirs, baseDir: this.task.options.compileDest }
222
232
 
223
233
  const toHdbtabledata = cds.compile.to.hdbtabledata ?? require(path.join(cds.home, 'bin/build/provider/hana/2tabledata')) // cds@6 compatibility
@@ -229,7 +239,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
229
239
  let tableDataPath = csvFolder.startsWith(this.task.options.compileDest) ? csvFolder : this.task.options.compileDest
230
240
  tableDataPath = path.join(tableDataPath, file)
231
241
  if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
232
- this._result.hana.push(path.join(relDest, file))
242
+ this._result.hana.add(path.relative(this.task.dest, tableDataPath))
233
243
  }
234
244
  promises.push(this.write(tableData).to(tableDataPath))
235
245
  }
@@ -254,7 +264,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
254
264
  *
255
265
  * @param {object} model The compiled csn model
256
266
  */
257
- async _compileToHana(model) {
267
+ async _compileToHana(model, workspaceFiles) {
258
268
  // compile to old format (.hdbcds) or new format (.hdbtable / .hdbview)
259
269
  const format = this.getBuildOption(DEPLOY_FORMAT) || cds.env.requires.db?.[DEPLOY_FORMAT] || cds.env.hana?.[DEPLOY_FORMAT]
260
270
  if (!cds.compile.to[format]) {
@@ -267,7 +277,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
267
277
  // compatibility with existing cds.compile.to.hdbtable plugins cds < 8.0.0
268
278
  return await this._compileToHdb(model, format)
269
279
  } else {
270
- return await this._compileToHdbmigration(model, format)
280
+ return await this._compileToHdbmigration(model, format, workspaceFiles)
271
281
  }
272
282
  }
273
283
 
@@ -285,7 +295,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
285
295
 
286
296
  hdiPlugins.add(suffix)
287
297
  if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
288
- this._result.hana.push(path.join(relDest, file))
298
+ this._result.hana.add(path.join(relDest, file))
289
299
  }
290
300
  promises.push(this.write(content).to(path.join(this.task.options.compileDest, file)))
291
301
  if (suffix === FILE_EXT_HDBTABLE) {
@@ -303,7 +313,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
303
313
  return hdiPlugins
304
314
  }
305
315
 
306
- async _compileToHdbmigration(model, format) {
316
+ async _compileToHdbmigration(model, format, workspaceFiles) {
307
317
  const hdiPlugins = new Set()
308
318
  const relDestDir = path.relative(this.task.dest, this.task.options.compileDest)
309
319
  const relDbDestDir = path.relative(cds.root, this.task.options.compileDest)
@@ -326,33 +336,37 @@ class HanaBuildPlugin extends InternalBuildPlugin {
326
336
 
327
337
  for (const { name, suffix, content, changed } of definitions) {
328
338
  hdiPlugins.add(suffix)
329
- const file = name + suffix
339
+ const fileName = name + suffix
330
340
  if (suffix === FILE_EXT_HDBMIGRATIONTABLE) {
331
- migrationTableFiles.push(path.join(relDbSrcDir, file))
332
- if (changed) {
333
- if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
334
- this._result.hana.push(path.join("src", file))
341
+ // Skip the .hdbmigrationtable file if its origin is a different workspace.
342
+ // The workspace build copies artifacts located in 'db/src/**' workspace folders.
343
+ if (!workspaceFiles?.find(wsFile => wsFile.fileName === fileName)) {
344
+ migrationTableFiles.push(path.join(relDbSrcDir, fileName))
345
+ if (changed) {
346
+ if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
347
+ this._result.hana.add(path.join("src", fileName))
348
+ }
349
+ promises.push(this.write(content).to(path.join(dbSrcDir, fileName)))
350
+ } else {
351
+ DEBUG?.(`no change, keep existing ${fileName}`)
335
352
  }
336
- promises.push(this.write(content).to(path.join(dbSrcDir, file)))
337
- } else {
338
- DEBUG?.(`no change, keep existing ${file}`)
339
353
  }
340
354
  } else {
341
355
  if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
342
- this._result.hana.push(path.join(relDestDir, file))
356
+ this._result.hana.add(path.join(relDestDir, fileName))
343
357
  }
344
- promises.push(this.write(content).to(path.join(this.task.options.compileDest, file)))
358
+ promises.push(this.write(content).to(path.join(this.task.options.compileDest, fileName)))
345
359
  if (suffix === FILE_EXT_HDBTABLE) {
346
360
  // issue an error in case a .hdbmigrationtable file already exists
347
361
  if (fs.existsSync(path.join(dbSrcDir, name + FILE_EXT_HDBMIGRATIONTABLE))) {
348
- validationError = new BuildError(`Multiple files exist defining the same HANA artifact - [${path.join(relDbSrcDir, name + FILE_EXT_HDBMIGRATIONTABLE)}, ${path.join(relDbDestDir, file)}].\nEither annotate the model entity using @cds.persistence.journal or undeploy the file [${path.join('src', name + FILE_EXT_HDBMIGRATIONTABLE)}] using an undeploy.json file.`)
362
+ validationError = new BuildError(`Multiple files exist defining the same HANA artifact - [${path.join(relDbSrcDir, name + FILE_EXT_HDBMIGRATIONTABLE)}, ${path.join(relDbDestDir, fileName)}].\nEither annotate the model entity using @cds.persistence.journal or undeploy the file [${path.join('src', name + FILE_EXT_HDBMIGRATIONTABLE)}] using an undeploy.json file.`)
349
363
  }
350
364
  }
351
365
  }
352
366
  }
353
- // the last-dev CSN shall only be updated, if all .hdbmigrationtable files could be successfully updated
354
- // of course, .hdbmigrationtable files already written would need to be manually reverted before the
355
- // cds build command is again executed
367
+ // The last-dev CSN shall only be updated, if all .hdbmigrationtable files could be successfully updated.
368
+ // Of course, .hdbmigrationtable files already written would need to be manually reverted before the
369
+ // cds build command is again executed.
356
370
  await Promise.all(promises)
357
371
 
358
372
  try {
@@ -531,35 +545,38 @@ class HanaBuildPlugin extends InternalBuildPlugin {
531
545
  }
532
546
  }
533
547
 
534
-
535
548
  /**
536
- * Copy native hana content from workspaces.
549
+ * Returns all hana native artifacts located in db/src/** workspace folders.
550
+ * @returns {Array} Array of workspace files, each entry contains the db src directory,
551
+ * the relative file path and the file name.
537
552
  */
538
- async _addWorkspaceContent() {
539
- const srcPaths = new Set()
553
+ async _getWorkspaceFiles() {
554
+ const dbPaths = new Set()
540
555
  const workspaces = await getWorkspaces()
541
556
 
542
557
  workspaces.forEach(workspace => {
543
558
  const dbSrc = path.join(cds.root, workspace, 'db/src')
544
559
  const dbCfg = path.join(cds.root, workspace, 'db/cfg')
545
560
  if (fs.existsSync(dbSrc)) {
546
- srcPaths.add(dbSrc)
561
+ dbPaths.add(dbSrc)
547
562
  }
548
563
  if (fs.existsSync(dbCfg)) {
549
- srcPaths.add(dbCfg)
564
+ dbPaths.add(dbCfg)
550
565
  }
551
566
  })
552
- DEBUG?.(`Copying hana native artifacts from folders '${[...srcPaths].map(f => path.relative(cds.root, f)).join(', ')}'`)
553
- const dest = path.join(this.task.dest, 'src/gen')
554
- for (const src of srcPaths) {
555
- await this.copyNativeContent(src, dest, (entry) => {
567
+ const workspaceFiles = [...dbPaths].reduce((acc, src) => {
568
+ const files = getFiles(src, (entry) => {
556
569
  if (entry.match(/(\/|\\)gen(\/|\\)?$/)) {
557
570
  // skip gen folder content
558
571
  return false
559
572
  }
560
573
  return true
561
574
  })
562
- }
575
+ files.forEach(file => acc.add({ dir: src, filePath: path.relative(src, file), fileName: path.basename(file) }))
576
+ return acc
577
+ }, new Set())
578
+
579
+ return [...workspaceFiles]
563
580
  }
564
581
 
565
582
  static async _readTemplateAsJson(template) {
@@ -5,7 +5,7 @@ const { hasJavaNature, getDefaultModelOptions, BuildError, hasOptionValue, norma
5
5
  const BuildTaskProvider = require('../buildTaskProvider')
6
6
  const { FILE_EXT_CDS, BUILD_TASK_HANA, BUILD_TASK_FIORI, BUILD_TASK_JAVA, BUILD_TASK_JAVA_CF, BUILD_TASK_NODEJS, BUILD_TASK_NODE_CF, BUILD_TASK_MTX,
7
7
  BUILD_TASKS, BUILD_TASK_MTX_SIDECAR, MTX_SIDECAR_FOLDER, BUILD_TASK_MTX_EXTENSION, NODEJS_MODEL_EXCLUDE_LIST,
8
- IGNORE_DEFAULT_MODELS, CONTENT_WS } = require("../constants")
8
+ IGNORE_DEFAULT_MODELS, OPTION_WS } = require("../constants")
9
9
  const ResourcesTarBuilder = require('./mtx/resourcesTarBuilder')
10
10
  const DEBUG = cds.debug('cli|build')
11
11
 
@@ -285,7 +285,7 @@ class InternalBuildTaskProvider extends BuildTaskProvider {
285
285
  }
286
286
 
287
287
  static async _setDefaultModel(task) {
288
- const ws = hasOptionValue(task.options?.[CONTENT_WS], true) || cds.cli.options?.ws
288
+ const ws = hasOptionValue(task.options?.[OPTION_WS], true) || cds.cli.options?.[OPTION_WS]
289
289
  // REVISIT: filter nodejs bootstrap service models for Java - issues/12770#issuecomment-1805719
290
290
  const filter = hasJavaNature() ? (m) => !NODEJS_MODEL_EXCLUDE_LIST.includes(m) : null
291
291
  let taskModelPaths = task.options?.model
@@ -45,7 +45,7 @@ class MtxSidecarBuildPlugin extends NodejsBuildPlugin {
45
45
  */
46
46
  async _buildNodeApp(sidecarEnv) {
47
47
  const destSidecar = this.task.dest
48
- const destSidecarSrc = path.join(destSidecar, cds.env.folders.srv)
48
+ const destSidecarSrc = path.join(destSidecar, sidecarEnv.folders.srv)
49
49
  const model = this._compileSidecarSync(sidecarEnv)
50
50
  await this.compileToJson(model, path.join(destSidecarSrc, DEFAULT_CSN_FILE_NAME))
51
51
  await this.collectLanguageBundles(model, path.join(destSidecarSrc, getI18nDefaultFolder()))
@@ -129,8 +129,15 @@ class MtxSidecarBuildPlugin extends NodejsBuildPlugin {
129
129
  this.pushMessage(`CDS models [${unresolved.join(', ')}] required by MTX sidecar cannot be resolved. Make sure up-to-date versions of the missing npm modules are installed.`, ERROR)
130
130
  }
131
131
 
132
- // synchronous compilation
133
- return cds.load(modelFilePaths, { sync: true, ...this.options() })
132
+ // using synchronous compilation as cds.root and cds.env have been switched to mtx/sidecar
133
+ // using different messages array ensures that cds compilation doesn't fail because of
134
+ // existing compilation error messages that have been downgraded to warnings
135
+ // see cap/issues/issues/16401#issuecomment-7205397
136
+ const messages = []
137
+ const model = cds.load(modelFilePaths, { sync: true, messages })
138
+ messages.forEach(msg => this.messages.push(msg))
139
+
140
+ return model
134
141
  } finally {
135
142
  // restore project scope
136
143
  cds.root = root
@@ -5,7 +5,8 @@ const cmd = require('../../../util/command');
5
5
  const EdmxBuildPlugin = require('../edmxBuildPlugin')
6
6
  const { BuildError, getWorkspaces } = require('../../util')
7
7
  const { OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM, ODATA_VERSION_V2, FOLDER_GEN, CONTENT_EDMX, CONTENT_PACKAGELOCK_JSON,
8
- CONTENT_NPMRC, CONTENT_CDSRC_JSON, CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, FLAVOR_LOCALIZED_EDMX } = require('../../constants')
8
+ CONTENT_NPMRC, CONTENT_CDSRC_JSON, CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, FLAVOR_LOCALIZED_EDMX,
9
+ OPTION_WS_PACK} = require('../../constants')
9
10
  const { WARNING } = EdmxBuildPlugin
10
11
  const DEBUG = cds.debug('cli|build')
11
12
 
@@ -36,7 +37,7 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
36
37
  }
37
38
  // by default model contains all features
38
39
  const model = await this.model()
39
- if (!model && !this.context.options['ws-pack']) {
40
+ if (!model && !this.context.options[OPTION_WS_PACK]) {
40
41
  return
41
42
  }
42
43
  if (model) {
@@ -55,7 +56,7 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
55
56
  if (this.isStagingBuild() && this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM)) {
56
57
  const srcSrv = this.task.src === cds.root ? path.resolve(this.task.src, cds.env.folders.srv) : this.task.src
57
58
  await this._copyNativeContent(cds.root, srcSrv, destRoot, destSrv)
58
- if (this.context.options['ws-pack']) {
59
+ if (this.context.options[OPTION_WS_PACK]) {
59
60
  await this._packWorkspaceDependencies(destRoot)
60
61
  }
61
62
  }
@@ -2,12 +2,13 @@ const cds = require('../cds')
2
2
 
3
3
  module.exports = function to_mermaid (model, options={}) {
4
4
  options = Object.assign({
5
- assocnames: false, // include association names
6
- elements : false, // include all elements { falsy | 'all'|true | 'keys' }
7
- min : false, // minify the model, i.e. remove unused definitions, available in CLI
8
- namespaces: true, // group entities by namespace/service
9
- queries : false, // include relations for queries
10
- service : undefined // filter service by name, available in CLI
5
+ assocnames: false, // include association names
6
+ elements : false, // include all elements { falsy | 'all'|true | 'keys' }
7
+ min : false, // minify the model, i.e. remove unused definitions, available in CLI
8
+ namespaces: true, // group entities by namespace/service
9
+ queries : false, // include relations for queries
10
+ service : undefined, // filter service by name, available in CLI
11
+ direction : undefined // direction of the graph
11
12
  }, cds.env.mermaid ?? {}, options)
12
13
 
13
14
  const elemOpts = options.elements === 'none' ? false : options.elements
@@ -27,6 +28,7 @@ module.exports = function to_mermaid (model, options={}) {
27
28
  const entityFilter = e => !e.name.startsWith('cds.') && !e.name.endsWith('.texts')
28
29
 
29
30
  let diag = ''
31
+ const direction = options.direction ? ` direction ${options.direction}\n` : ''
30
32
 
31
33
  // if requested, filter by one service
32
34
  model.all('service')
@@ -56,8 +58,10 @@ module.exports = function to_mermaid (model, options={}) {
56
58
  })
57
59
 
58
60
  // create namespace groups and classes
59
- Object.keys(namespaces).forEach(ns => {
60
- if (!diag.length) diag = `classDiagram\n`
61
+ Object.keys(namespaces)
62
+ .filter (ns => namespaces[ns].length) // empty namespaces can cause rendering errors, so skip them
63
+ .forEach(ns => {
64
+ if (!diag.length) diag = `classDiagram\n${direction}`
61
65
  diag += ` namespace ${ns.replaceAll('.','_')} {\n`
62
66
  namespaces[ns].forEach (e => {
63
67
  const simpleName = e.name.slice(e.name.lastIndexOf('.')+1)
@@ -65,7 +69,7 @@ module.exports = function to_mermaid (model, options={}) {
65
69
  diag += `["${simpleName}"]` // simple name as label
66
70
  if (elemOpts) {
67
71
  diag += ` {\n`
68
- e.elements.forEach (el => {
72
+ e.elements?.forEach (el => {
69
73
  if ((elemOpts === 'all' || elemOpts === true) || (el.key && elemOpts == 'keys')) {
70
74
  diag += ` +${el._type?.replace('cds.','')} ${el.name}`
71
75
  diag += `\n`
@@ -85,8 +89,8 @@ module.exports = function to_mermaid (model, options={}) {
85
89
  ? Array.from(seenEntities).map(name => model.definitions[name])
86
90
  : model.all('entity').filter (entityFilter) // if no grouping and no service filter is on
87
91
  entities.forEach(e => {
88
- if (!diag.length) diag = `classDiagram\n`
89
- e.elements.forEach (el => {
92
+ if (!diag.length) diag = `classDiagram\n${direction}`
93
+ e.elements?.forEach (el => {
90
94
  if (el instanceof cds.Association) {
91
95
  if (el.target.endsWith('.texts')) return
92
96
  const type = el.type === 'cds.Composition' ? '*-->' : '-->'
@@ -100,9 +104,20 @@ module.exports = function to_mermaid (model, options={}) {
100
104
  })
101
105
  // add relations for queries
102
106
  if (e.query && options.queries) {
103
- diag += ` \`${e.name}\` ..> \`${e.query.target.name}\`\n`
107
+ resolveTargets(e.query).forEach(t => {
108
+ diag += ` \`${e.name}\` ..> \`${t}\`\n`
109
+ })
104
110
  }
105
111
  })
106
112
 
107
113
  return diag.trim()
108
114
  }
115
+
116
+ function resolveTargets (query) {
117
+ if (query.target) return [ query.target.name ] // simple query, like projection on
118
+
119
+ // SELECT: { from: { args: [{ ref: ['t1'] }, { ref: ['t2'] }], join: ..., on: ... } }
120
+ const targets = query.SELECT?.from?.args?.filter(arg => arg.ref?.length).map (arg => arg.ref[0])
121
+
122
+ return targets
123
+ }
@@ -6,7 +6,6 @@ const cds = require('../../../lib/cds');
6
6
  const { exists, read } = cds.utils;
7
7
 
8
8
  const { build } = require('../../build');
9
- const buildConstants = require('../../build/constants');
10
9
 
11
10
  const hdi = require('./hdiDeployUtil');
12
11
  const cfUtil = require('../../util/cf');
@@ -16,6 +15,7 @@ const mtaUtil = require('./mtaUtil');
16
15
  const { bold, info, warn } = require('../../util/term');
17
16
 
18
17
  const { bind, storeBindings } = require('../../bind');
18
+ const { BUILD_TASK_HANA, OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM, OUTPUT_MODE_RESULT } = require('../../build/constants');
19
19
 
20
20
  const DEBUG = cds.debug('cli');
21
21
 
@@ -73,13 +73,13 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
73
73
  vcapEnv.TARGET_CONTAINER = process.env.TARGET_CONTAINER;
74
74
  }
75
75
 
76
- for (const { task } of buildResults) {
76
+ for (const { task, result } of buildResults) {
77
77
  if (dry) {
78
- const model = await cds.load(task.src);
79
- const data = cds.compile.to.hdbtable(model);
80
- for (const [src, { file }] of data) {
81
- console.log(`-- ${file}`);
82
- console.log(src);
78
+ const sortedResult = Array.from(result.hana).sort();
79
+ for (const file of sortedResult) {
80
+ const srcPath = path.join(result.dest, file);
81
+ console.log(`-- ${path.relative(cds.root, srcPath)}`);
82
+ console.log(await read(srcPath));
83
83
  console.log();
84
84
  }
85
85
  } else {
@@ -221,7 +221,9 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
221
221
 
222
222
  async _build() {
223
223
  const buildTaskOptions = {
224
- for: buildConstants.BUILD_TASK_HANA
224
+ for: BUILD_TASK_HANA,
225
+ src: cds.env.folders.db,
226
+ [OUTPUT_MODE]: OUTPUT_MODE_FILESYSTEM | OUTPUT_MODE_RESULT
225
227
  }
226
228
 
227
229
  console.log(`Running build`);