@sap/cds-dk 9.6.1 → 9.7.1

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 (87) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/bin/add.js +5 -2
  3. package/bin/cds.js +10 -25
  4. package/bin/data-products/import.js +3 -0
  5. package/bin/debug.js +76 -16
  6. package/bin/env.js +2 -1
  7. package/bin/export.js +18 -13
  8. package/bin/help/export.md +2 -1
  9. package/bin/import.js +21 -18
  10. package/bin/init.js +8 -1
  11. package/bin/minify.js +11 -12
  12. package/bin/mock.js +17 -9
  13. package/bin/repl.js +2 -2
  14. package/bin/up.js +34 -4
  15. package/bin/version.js +203 -131
  16. package/lib/build/buildTaskEngine.js +4 -0
  17. package/lib/build/buildTaskFactory.js +6 -36
  18. package/lib/build/index.js +10 -10
  19. package/lib/build/plugin.js +8 -5
  20. package/lib/build/plugins/helm/files/subcharts/content-deployment/Chart.yaml +3 -1
  21. package/lib/build/plugins/helm/files/subcharts/content-deployment/templates/_helpers.tpl +1 -1
  22. package/lib/build/plugins/helm/files/subcharts/content-deployment/templates/job.yaml +8 -0
  23. package/lib/build/plugins/helm/files/subcharts/content-deployment/values.schema.json +18 -0
  24. package/lib/build/plugins/helm/files/subcharts/content-deployment/values.yaml +11 -1
  25. package/lib/build/plugins.js +8 -16
  26. package/lib/build/provider/fiori/index.js +3 -3
  27. package/lib/build/provider/hana/index.js +16 -19
  28. package/lib/build/provider/internalBuildPlugin.js +5 -4
  29. package/lib/build/provider/internalBuildTaskProvider.js +26 -88
  30. package/lib/build/provider/mtx/index.js +0 -8
  31. package/lib/build/provider/mtx-sidecar/index.js +88 -80
  32. package/lib/build/provider/nodejs/index.js +3 -8
  33. package/lib/build/util.js +52 -115
  34. package/lib/deploy/to-hana/hana.js +117 -109
  35. package/lib/env/schemas/cds-rc.js +0 -4
  36. package/lib/import/odata/v4parser.js +80 -56
  37. package/lib/init/constants.js +5 -2
  38. package/lib/init/index.js +48 -41
  39. package/lib/init/merge.js +7 -2
  40. package/lib/init/projectReader.js +3 -8
  41. package/lib/init/template/ams/index.js +1 -4
  42. package/lib/init/template/attachments/files/package.json.hbs +1 -1
  43. package/lib/init/template/completion/completionSetup.js +1 -1
  44. package/lib/init/template/data/as-csv.js +4 -1
  45. package/lib/init/template/data/index.js +1 -1
  46. package/lib/init/template/extension/index.js +4 -4
  47. package/lib/init/template/hana/files/package.json.hbs +3 -3
  48. package/lib/init/template/handler/index.js +1 -1
  49. package/lib/init/template/helm/index.js +3 -3
  50. package/lib/init/template/helm-unified-runtime/index.js +5 -1
  51. package/lib/init/template/html5-repo/files/mta.yaml.hbs +1 -1
  52. package/lib/init/template/html5-repo/index.js +6 -5
  53. package/lib/init/template/ias/files/mta.yaml.hbs +3 -3
  54. package/lib/init/template/initial/files/.vscode/tasks.json +10 -0
  55. package/lib/init/template/{nodejs/files/README.md → initial/files/readme.md} +2 -3
  56. package/lib/init/template/initial/index.js +15 -0
  57. package/lib/init/template/java/index.js +4 -11
  58. package/lib/init/template/kyma/index.js +21 -20
  59. package/lib/init/template/lint/index.js +3 -1
  60. package/lib/init/template/mta/index.js +0 -5
  61. package/lib/init/template/multitenancy/files/package.sidecar.json.hbs +1 -2
  62. package/lib/init/template/multitenancy/files/values.yaml.hbs +30 -0
  63. package/lib/init/template/nodejs/files/.vscode/tasks.json +1 -14
  64. package/lib/init/template/nodejs/files/package.json.hbs +2 -4
  65. package/lib/init/template/nodejs/index.js +8 -12
  66. package/lib/init/template/sample/index.js +5 -6
  67. package/lib/init/template/sqlite/files/package.json.hbs +7 -1
  68. package/lib/init/template/test/index.js +1 -1
  69. package/lib/init/template/tiny-sample/files/nodejs/db/data/CatalogService.Books.csv +6 -0
  70. package/lib/init/template/tiny-sample/files/nodejs/srv/cat-service.cds +5 -0
  71. package/lib/init/template/tiny-sample/index.js +2 -2
  72. package/lib/init/template/workzone-standard/files/mta.yaml.hbs +6 -0
  73. package/lib/init/template/workzone-standard/index.js +13 -3
  74. package/lib/init/template/xsuaa/index.js +1 -1
  75. package/lib/init/validate.js +11 -6
  76. package/lib/util/checksum.js +2 -2
  77. package/lib/util/fs.js +109 -53
  78. package/lib/util/glob.js +2 -1
  79. package/lib/util/term.js +1 -7
  80. package/lib/util/write.js +2 -4
  81. package/lib/watch/watched.js +1 -1
  82. package/npm-shrinkwrap.json +233 -266
  83. package/package.json +2 -2
  84. package/lib/init/template/nodejs/files/.vscode/settings.json +0 -11
  85. package/lib/init/template/tiny-sample/files/nodejs/db/data/my.bookshop-Books.csv +0 -3
  86. package/lib/init/template/tiny-sample/files/nodejs/db/schema.cds +0 -7
  87. package/lib/init/template/tiny-sample/files/nodejs/srv/cat-service.cds.hbs +0 -5
@@ -1,19 +1,19 @@
1
- const path = require('path')
2
1
  const cds = require('../../../cds')
3
- const fs = require('fs')
4
- const { FOLDER_GEN, DEFAULT_CSN_FILE_NAME, MTX_SIDECAR_DB_VALIDATION, CONTENT_EDMX, FLAVOR_LOCALIZED_EDMX } = require('../../constants')
5
- const NodejsBuildPlugin = require('../nodejs')
2
+ const { exists, path } = cds.utils
3
+ const { SEVERITY_ERROR: ERROR, FOLDER_GEN, MTX_SIDECAR_DB_VALIDATION, CONTENT_EDMX, FLAVOR_LOCALIZED_EDMX } = require('../../constants')
6
4
  const ResourcesTarBuilder = require('../mtx/resourcesTarBuilder')
7
- const { ERROR } = NodejsBuildPlugin
8
5
  const { relativePaths, BuildError, resolveRequiredSapModels, getI18nDefaultFolder } = require('../../util')
9
6
  const DEBUG = cds.debug('cli|build')
10
7
 
11
8
  const DEFAULT_MAIN_FOLDER = "_main"
12
9
 
13
- class MtxSidecarBuildPlugin extends NodejsBuildPlugin {
10
+ const [major, minor] = cds.version.split('.').map(Number)
11
+ const COMPAT = major === 9 && minor < 7 // cds10: remove
12
+
13
+ module.exports = class extends require('../nodejs') {
14
+
14
15
  get priority() {
15
- // must run after 'hana' build task
16
- return super.priority - 10
16
+ return super.priority - 10 // must run after 'hana' build task
17
17
  }
18
18
  init() {
19
19
  super.init()
@@ -23,43 +23,51 @@ class MtxSidecarBuildPlugin extends NodejsBuildPlugin {
23
23
  }
24
24
 
25
25
  /**
26
- * Builds the mtx sidecar app consisting of:
27
- * - nodejs app model defined by the required sidecar services
28
- * - main app model defined by the build task's model options including feature models and resources TAR
26
+ * Builds the MTX sidecar app consisting of:
27
+ * - Node.js app model defined by the required sidecar services
28
+ * - Main app model defined by the build task's model options, including feature models and resources TAR
29
29
  *
30
30
  * build.target=".": 'dest' -> 'model-provider/gen'
31
31
  * build.target="gen": 'dest' -> 'gen/model-provider'
32
32
  */
33
33
  async build() {
34
- // nodejs app parts have to be built using sidecar env
35
- const sidecarEnv = cds.env.for("cds", this.task.src)
36
- // build main application
37
- await this._buildMainApp(sidecarEnv)
38
- // build node application
39
- await this._buildNodeApp(sidecarEnv)
34
+ // REVISIT: required to load plugins from mtx/sidecar, not the main app for the sidecar CSN
35
+ const oldRoot = cds.root
36
+ cds.root = this.task.src
37
+ const sidecarEnv = cds.env.for('cds', this.task.src)
38
+ cds.root = oldRoot
39
+
40
+ const [rMain, rNode] = await Promise.allSettled([
41
+ this._buildMainApp(sidecarEnv),
42
+ this._buildSidecarApp(sidecarEnv)
43
+ ])
44
+ if (rMain.status === 'rejected') throw rMain.reason
45
+ if (rNode.status === 'rejected') throw rNode.reason
40
46
  }
41
47
 
42
48
  /**
43
- * Builds the mtx sidecar nodejs app parts.
44
- * @param {object} sidecarEnv cds env based on the sidecar dir
49
+ * Builds the MTX sidecar Node.js app parts.
50
+ * @param {import('../../../cds').env} sidecarEnv `cds.env` based on the MTX sidecar directory.
45
51
  */
46
- async _buildNodeApp(sidecarEnv) {
47
- const destSidecar = this.task.dest
48
- const destSidecarSrc = path.join(destSidecar, sidecarEnv.folders.srv)
49
- const model = this._compileSidecarSync(sidecarEnv)
50
- await this.compileToJson(model, path.join(destSidecarSrc, DEFAULT_CSN_FILE_NAME))
51
- await this.collectLanguageBundles(model, path.join(destSidecarSrc, getI18nDefaultFolder()))
52
- await this.copySrvContent(this.task.src, destSidecar, destSidecar)
53
-
52
+ async _buildSidecarApp(sidecarEnv) {
53
+ const { dest, src } = this.task
54
+ const model = COMPAT ? this._compatCompileSidecarSync(sidecarEnv) : this._compileSidecarSync(sidecarEnv)
55
+ const srv = path.join(dest, sidecarEnv.folders.srv)
56
+ const promises = [
57
+ this.compileToJson(model, path.join(srv, 'csn.json')),
58
+ this.collectLanguageBundles(model, path.join(srv, getI18nDefaultFolder())),
59
+ this.copySrvContent(src, dest, dest)
60
+ ]
54
61
  // copy .npmrc from project root, if it exists and no .npmrc exists in the sidecar
55
- if (!fs.existsSync(path.join(destSidecar, '.npmrc')) && fs.existsSync(path.join(cds.root, '.npmrc'))) {
56
- await this.copy(path.join(cds.root, '.npmrc')).to(path.join(destSidecar, '.npmrc'))
62
+ if (!exists(path.join(dest, '.npmrc')) && exists('.npmrc')) {
63
+ promises.push(this.copy(path.join(cds.root, '.npmrc')).to(path.join(dest, '.npmrc')))
57
64
  }
65
+ await Promise.all(promises)
58
66
  }
59
67
 
60
68
  /**
61
69
  * Builds the main app parts containing base model CSN with feature CSNs and resources TAR.
62
- * @param {object} sidecarEnv cds env based on the sidecar dir
70
+ * @param {import('../../../cds').env} sidecarEnv `cds.env` based on the MTX sidecar directory.
63
71
  */
64
72
  async _buildMainApp(sidecarEnv) {
65
73
  let main = sidecarEnv.requires['cds.xt.ModelProviderService']?.root
@@ -78,85 +86,85 @@ class MtxSidecarBuildPlugin extends NodejsBuildPlugin {
78
86
  main = DEFAULT_MAIN_FOLDER
79
87
  }
80
88
 
81
- const destRoot = this.task.dest
82
- const destMain = path.join(destRoot, main)
89
+ const destMain = path.join(this.task.dest, main)
83
90
  const destMainSrv = path.join(destMain, cds.env.folders.srv)
84
91
  const model = await this.model()
85
92
  if (!model) {
86
93
  return
87
94
  }
95
+
88
96
  const { dictionary, sources } = await this.compileAll(model, destMainSrv, destMain)
89
- // await this.collectAllLanguageBundles(dictionary, sources, destMainSrv, destMain)
90
- await this.collectAllLanguageBundles(dictionary, sources, destMain, destMain)
97
+
98
+ const promises = []
99
+ promises.push(this.collectAllLanguageBundles(dictionary, sources, destMainSrv, destMain))
100
+ // promises.push(this.collectAllLanguageBundles(dictionary, sources, destMain, destMain))
91
101
 
92
102
  if (!this.hasBuildOption(CONTENT_EDMX, false)) {
93
- //inferred model expected by cds.compile.to.edmx()
103
+ // inferred model expected by cds.compile.to.edmx()
94
104
  const compileOptions = { [FLAVOR_LOCALIZED_EDMX]: this.hasBuildOption(FLAVOR_LOCALIZED_EDMX, true) } // disabled by default
95
105
  const baseModel = cds.compiler.compileSources({ 'base.json': dictionary.base }, { messages: this.messages })
96
- await this.compileToEdmx(baseModel, path.join(destMainSrv, 'odata', cds.env.odata.version), compileOptions)
106
+ promises.push(this.compileToEdmx(baseModel, path.join(destMainSrv, 'odata', cds.env.odata.version), compileOptions))
97
107
  }
98
108
 
99
109
  // create resources TAR
100
110
  // resources are determined based on available database build task, SQLite as fallback
101
- await new ResourcesTarBuilder(this).createTar(destMain, model)
111
+ promises.push(new ResourcesTarBuilder(this).createTar(destMain, model))
102
112
 
103
113
  // copy package.json and .cdsrc.json from project root
104
- await this._copyMainConfigFiles(cds.root, destMain)
114
+ const packageJson = path.join(cds.root, 'package.json')
115
+ const cdsrcJson = path.join(cds.root, '.cdsrc.json')
116
+ if (exists('package.json')) {
117
+ promises.push(this.copy(packageJson).to(path.join(destMain, 'package.json')))
118
+ }
119
+ if (exists('.cdsrc.json')) {
120
+ promises.push(this.copy(cdsrcJson).to(path.join(destMain, '.cdsrc.json')))
121
+ }
122
+ return Promise.all(promises)
105
123
  }
106
124
 
107
125
  /**
108
- * Synchronous compilation using the sidecar context.
109
- * @param {object} sidecarEnv cds env based on the sidecar dir
110
- * @returns the compiled mtx sidecar CSN
126
+ * Synchronous compilation using the MTX sidecar context.
127
+ * @param {import('../../../cds').env} env `cds.env` based on the MTX sidecar directory.
128
+ * @returns the compiled MTX sidecar CSN
111
129
  */
112
- _compileSidecarSync(sidecarEnv) {
130
+ _compileSidecarSync(env) {
131
+ const options = { env, root: this.task.src }
132
+ const modelPaths = COMPAT ? cds.resolve('*', false) : cds.resolve('*', { ...options, dry: true })
133
+ const modelFilePaths = COMPAT ? cds.resolve(modelPaths) : cds.resolve(modelPaths, options)
134
+ if (!modelFilePaths || modelFilePaths.length === 0) {
135
+ throw new BuildError("No CDS models found in MTX sidecar. Run the 'npm install' command to install up-to-date versions of the required packages.")
136
+ }
137
+ DEBUG?.(`sidecar model: ${relativePaths(cds.root, modelFilePaths).join(", ")}`)
138
+
139
+ // check whether all models belonging to the @sap namespace can be resolved
140
+ const unresolved = resolveRequiredSapModels(modelPaths)
141
+ if (unresolved.length > 0) {
142
+ // log error, but don't fail
143
+ this.pushMessage(`CDS models [${unresolved.join(', ')}] required by MTX sidecar cannot be resolved. Run the 'npm install' command to install up-to-date versions of the required packages.`, ERROR)
144
+ }
145
+
146
+ // using synchronous compilation as cds.root and cds.env have been switched to mtx/sidecar
147
+ // using different messages array ensures that cds compilation doesn't fail because of
148
+ // existing compilation error messages that have been downgraded to warnings
149
+ // see cap/issues/issues/16401#issuecomment-7205397
150
+ const messages = []
151
+ const model = cds.load(modelFilePaths, { sync: true, messages })
152
+ messages.forEach(msg => this.messages.push(msg))
153
+ return model
154
+ }
155
+
156
+ // cds10: remove
157
+ _compatCompileSidecarSync(sidecarEnv) {
113
158
  const env = cds.env
114
159
  const root = cds.root
115
160
  try {
116
161
  cds.root = this.task.src
117
162
  cds.env = sidecarEnv
118
- const modelPaths = cds.resolve('*', false)
119
- const modelFilePaths = cds.resolve(modelPaths)
120
-
121
- if (!modelFilePaths || modelFilePaths.length === 0) {
122
- throw new BuildError("No CDS models found in MTX sidecar. Run the 'npm install' command to install up-to-date versions of the required packages.")
123
- }
124
- DEBUG?.(`sidecar model: ${relativePaths(cds.root, modelFilePaths).join(", ")}`)
125
-
126
- // check whether all models belonging to the @sap namespace can be resolved
127
- const unresolved = resolveRequiredSapModels(modelPaths)
128
- if (unresolved.length > 0) {
129
- // log error, but don't fail
130
- this.pushMessage(`CDS models [${unresolved.join(', ')}] required by MTX sidecar cannot be resolved. Run the 'npm install' command to install up-to-date versions of the required packages.`, ERROR)
131
- }
132
-
133
- // using synchronous compilation as cds.root and cds.env have been switched to mtx/sidecar
134
- // using different messages array ensures that cds compilation doesn't fail because of
135
- // existing compilation error messages that have been downgraded to warnings
136
- // see cap/issues/issues/16401#issuecomment-7205397
137
- const messages = []
138
- const model = cds.load(modelFilePaths, { sync: true, messages })
139
- messages.forEach(msg => this.messages.push(msg))
140
-
141
- return model
163
+ return this._compileSidecarSync(sidecarEnv)
142
164
  } finally {
143
165
  // restore project scope
144
166
  cds.root = root
145
167
  cds.env = env
146
168
  }
147
169
  }
148
-
149
- async _copyMainConfigFiles(src, dest) {
150
- const packageJson = path.join(src, 'package.json')
151
- const cdsrcJson = path.join(src, '.cdsrc.json')
152
- const promises = []
153
- if (fs.existsSync(packageJson)) {
154
- promises.push(this.copy(packageJson).to(path.join(dest, 'package.json')))
155
- }
156
- if (fs.existsSync(cdsrcJson)) {
157
- promises.push(this.copy(cdsrcJson).to(path.join(dest, '.cdsrc.json')))
158
- }
159
- return promises
160
- }
161
170
  }
162
- module.exports = MtxSidecarBuildPlugin
@@ -23,9 +23,6 @@ const arrayify = x => Array.isArray(x) ? x : [x]
23
23
  class NodejsBuildPlugin extends EdmxBuildPlugin {
24
24
  init() {
25
25
  super.init()
26
- if (this.task.options.compileDest) {
27
- throw new BuildError("Option not supported - compileDest")
28
- }
29
26
  // fallback if src has been defined as '.'
30
27
  this.destSrv = this.isStagingBuild() ? path.resolve(this.task.dest, cds.env.folders.srv) : path.join(this.task.dest, FOLDER_GEN)
31
28
  }
@@ -53,8 +50,8 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
53
50
  if (model) {
54
51
  const { dictionary, sources } = await this.compileAll(model, destSrv, destRoot)
55
52
  // collect and write language bundles into single i18n.json file
56
- await this.collectAllLanguageBundles(dictionary, sources, destRoot, destRoot)
57
- // await this.collectAllLanguageBundles(dictionary, sources, destSrv, destRoot)
53
+ // await this.collectAllLanguageBundles(dictionary, sources, destRoot, destRoot)
54
+ await this.collectAllLanguageBundles(dictionary, sources, destSrv, destRoot)
58
55
 
59
56
  if (!this.hasBuildOption(CONTENT_EDMX, false)) {
60
57
  const compileOptions = { [FLAVOR_LOCALIZED_EDMX]: this.hasBuildOption(FLAVOR_LOCALIZED_EDMX, true) }
@@ -159,7 +156,7 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
159
156
 
160
157
  async _copyNativeContent(srcRoot, srcSrv, destRoot, destSrv) {
161
158
  // project/srv/** -> 'gen/srv/srv/**'
162
- const filesFilter = await this.copySrvContent(srcSrv, destRoot, this.hasBuildOption('flatDeployLayout', true) ? destRoot : destSrv)
159
+ const filesFilter = await this.copySrvContent(srcSrv, destRoot, destSrv)
163
160
 
164
161
  // project/* -> 'gen/srv/*'
165
162
  await this.copyProjectRootContent(srcRoot, destRoot, (entry) => !filesFilter.includes(path.basename(entry)))
@@ -300,8 +297,6 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
300
297
  static async _execNpmPack(dest, wsRoot, workspaces) {
301
298
  const args = ['pack', '-ws', '--json', `--pack-destination=${dest}`]
302
299
  workspaces.forEach(w => args.push(`-w=${w}`))
303
-
304
- DEBUG?.(`execute command: npm ${args.join(', ')}`)
305
300
  const result = await cmd.spawnCommand('npm', args, { cwd: wsRoot }, true, !!LOG._debug)
306
301
  DEBUG?.(result)
307
302
  return JSON.parse(result)
package/lib/build/util.js CHANGED
@@ -1,9 +1,10 @@
1
1
  // @ts-check
2
- const cp = require('node:child_process');
2
+ const cp = require('node:child_process')
3
3
  const fs = require('node:fs')
4
4
  const path = require('node:path')
5
5
 
6
6
  const cds = require('../cds')
7
+ const { exists } = cds.utils
7
8
  const execAsync = require('node:util').promisify(cp.exec)
8
9
  const { isRoot } = require('../util/fs')
9
10
 
@@ -14,37 +15,12 @@ const DEBUG = cds.debug('cli|build')
14
15
  // failsafe to avoid too deep recursions
15
16
  // increased from 2 to 5, adding a config options in the future
16
17
  const WS_MAX_DEPTH = 5
18
+ const FLATTEN_MAX_DEPTH = 20 // should suffice, was infinite in earlier versions
17
19
 
18
20
  /** @typedef {{col?: number, endCol?: number, line?: number, endLine?: number, file: string}} SourceLocation */
19
21
  /** @typedef {object[] | object} Credentials */
20
22
  /** @typedef {(string | NestedStringArray)[]} NestedStringArray */
21
23
 
22
- /**
23
- * @param {object} src - object to get property from
24
- * @param {string | string[]} segments - property path
25
- */
26
- function getProperty(src, segments) {
27
- segments = Array.isArray(segments) ? segments : segments.split('.')
28
- return segments.reduce((p, n) => p && p[n], src)
29
- }
30
-
31
- /**
32
- * @param {object} src - object to set property on
33
- * @param {string | string[]} segments - property path
34
- * @param {unknown} value - value to set
35
- */
36
- function setProperty(src, segments, value) {
37
- segments = Array.isArray(segments) ? segments : segments.split('.')
38
- segments.reduce((p, n, idx) => {
39
- if (segments.length === idx + 1) {
40
- p[n] = value
41
- } else if (p[n] === undefined) {
42
- p[n] = {}
43
- }
44
- return p[n]
45
- }, src)
46
- }
47
-
48
24
  /**
49
25
  * Returns whether this project is a java project or not.
50
26
  */
@@ -92,7 +68,7 @@ function relativePaths(root, qualifiedPaths) {
92
68
  * Returns a list of fully qualified model names belonging to the '@sap' namespace that cannot be resolved.
93
69
  * E.g. the module might NOT have been installed.
94
70
  * @param {string[]} modelPaths
95
- * @returns {string[]}
71
+ * @returns {boolean | string[]}
96
72
  */
97
73
  function resolveRequiredSapModels(modelPaths) {
98
74
  return Array.isArray(modelPaths) && modelPaths.filter(p => {
@@ -104,14 +80,35 @@ function resolveRequiredSapModels(modelPaths) {
104
80
  }
105
81
 
106
82
  /**
107
- * Returns the default model options for the given workspace.
83
+ * Returns the default model paths for the given workspace.
108
84
  * @param {boolean} ws - whether to include workspace models
109
- * @param {Function | undefined} filter - filter function for model paths
85
+ * @returns {Promise<string[]>} array of model paths
110
86
  */
111
- async function getDefaultModelOptions(ws = false, filter = undefined) {
112
- // Note: requires.toggles holds the actual kind representation and not the value 'true'
113
- const wildcards = cds.env.requires.toggles && cds.env.features.folders ? ['*', cds.env.features.folders] : '*'
114
- let paths = _filterModelPaths(cds.resolve(wildcards, false), filter)
87
+ async function getDefaultModelPaths(ws = false) {
88
+ const { toggles } = cds.env.requires, { folders } = cds.env.features
89
+ const wildcards = toggles && folders ? ['*', folders] : '*'
90
+ if (!cds.resolve.locations) return _compatGetDefaultModelPaths(ws, wildcards)
91
+
92
+ let paths = _filterModelPaths(cds.resolve.locations(wildcards))
93
+ if (ws) {
94
+ const workspaces = (await getWorkspaces())
95
+ .map(ws => ws.workspace)
96
+ workspaces.forEach(workspace => {
97
+ const root = path.join(cds.root, workspace)
98
+ const env = cds.env.for('cds', root)
99
+ const resolved = cds.resolve.locations(env.requires.toggles ? wildcards : '*', { env, root })
100
+ paths.push(..._filterModelPaths(resolved, { root, env })
101
+ .map(p => normalizePath(path.join(workspace, p))))
102
+ })
103
+ // exclude redundant built-in models starting with workspace path, e.g. 'books/@sap/cds-mtxs/srv/bootstrap'
104
+ paths = paths.filter(p => NODEJS_MODEL_EXCLUDE_LIST.includes(p) || !NODEJS_MODEL_EXCLUDE_LIST.some(ex => p.endsWith(ex)))
105
+ }
106
+ return paths
107
+ }
108
+
109
+ // cds10: remove
110
+ async function _compatGetDefaultModelPaths(ws, wildcards) {
111
+ let paths = _filterModelPaths(cds.resolve(wildcards, false))
115
112
 
116
113
  if (ws) {
117
114
  const workspaces = (await getWorkspaces())
@@ -122,15 +119,15 @@ async function getDefaultModelOptions(ws = false, filter = undefined) {
122
119
  workspaces.forEach(workspace => {
123
120
  cds.root = path.join(root, workspace)
124
121
  cds.env = cds.env.for('cds', cds.root)
125
- paths = paths.concat(_filterModelPaths(cds.resolve(cds.env.requires.toggles ? wildcards : '*', false), filter).map(p => normalizePath(path.join(workspace, p))))
122
+ const fromWorkspace = _filterModelPaths(cds.resolve(cds.env.requires.toggles ? wildcards : '*', false))
123
+ paths.push(...fromWorkspace.map(p => normalizePath(path.join(workspace, p))))
126
124
  })
127
125
  } finally {
128
126
  cds.root = root
129
127
  cds.env = env
130
128
  }
131
129
  // exclude redundant built-in models starting with workspace path, e.g. 'books/@sap/cds-mtxs/srv/bootstrap'
132
- paths = paths
133
- .filter(p => typeof(p) === 'string' && (NODEJS_MODEL_EXCLUDE_LIST.includes(p) || !NODEJS_MODEL_EXCLUDE_LIST.some(ex => p.endsWith(ex))))
130
+ paths = paths.filter(p => NODEJS_MODEL_EXCLUDE_LIST.includes(p) || !NODEJS_MODEL_EXCLUDE_LIST.some(ex => p.endsWith(ex)))
134
131
  }
135
132
  return paths
136
133
  }
@@ -143,7 +140,7 @@ async function getDefaultModelOptions(ws = false, filter = undefined) {
143
140
  /**
144
141
  * @overload
145
142
  * @param {unknown} dir
146
- * @returns {unknown}
143
+ * @returns {unknown}
147
144
  */
148
145
  /**
149
146
  * For valid paths replace '\\' with '/' and remove trailing '/'. Otherwise return as is
@@ -154,21 +151,6 @@ function normalizePath(dir) {
154
151
  return typeof dir === "string" ? dir.replace(/\\/g, '/').replace(/\/$/, '') : dir
155
152
  }
156
153
 
157
- /**
158
- * @param {NestedStringArray} modelPaths
159
- */
160
- function flatten(modelPaths) {
161
- // TODO: I guess this could be modelPaths.flat(Infinity) in the future
162
- return /** @type{string[]}*/(modelPaths.reduce((/** @type {string[]}*/acc, m) => {
163
- if (Array.isArray(m)) {
164
- acc = acc.concat(flatten(m))
165
- } else if (m) {
166
- acc.push(m)
167
- }
168
- return acc
169
- }, []))
170
- }
171
-
172
154
  function getI18nDefaultFolder() {
173
155
  // keep '_i18n' as preferred folder for compatibility reasons
174
156
  // if (cds.env.i18n.folders.includes('_i18n')) return '_i18n'
@@ -176,10 +158,6 @@ function getI18nDefaultFolder() {
176
158
  return cds.env.i18n.folders?.[0] ?? '_i18n'
177
159
  }
178
160
 
179
- async function pathExists(path) {
180
- return fs.promises.access(path).then(() => true).catch(() => false);
181
- }
182
-
183
161
  async function getWorkspacePaths() {
184
162
  const wsRoot = findWorkspaceRoot(cds.root)
185
163
  if (!wsRoot) {
@@ -246,15 +224,15 @@ async function getWorkspacesDeclarations (root) {
246
224
  * needs to be defined resulting in a corresponding symlink.
247
225
  * @returns {Promise<Workspace[]>} the paths are relative to 'wsRoot'.
248
226
  */
249
- async function getWorkspaces(wsRoot, resolve = false, devDependencies = true, visited = new Set()) {
250
- wsRoot ??= findWorkspaceRoot(cds.root)
227
+ async function getWorkspaces(wsRoot, resolve = false, devDependencies = true, visited = new Set(), root = cds.root) {
228
+ wsRoot ??= findWorkspaceRoot(root)
251
229
  if (!wsRoot) {
252
230
  return []
253
231
  }
254
232
  const pkgJsonWsRoot = require(path.join(wsRoot, 'package.json'))
255
233
  let pkgJson = pkgJsonWsRoot
256
- if (wsRoot !== cds.root) {
257
- pkgJson = require(path.join(cds.root, 'package.json'))
234
+ if (wsRoot !== root) {
235
+ pkgJson = require(path.join(root, 'package.json'))
258
236
  }
259
237
  const workspaceDependencies = []
260
238
  if (pkgJsonWsRoot.workspaces) {
@@ -271,11 +249,8 @@ async function getWorkspaces(wsRoot, resolve = false, devDependencies = true, vi
271
249
  if (subproject && !visited.has(pkg)) {
272
250
  visited.add(pkg)
273
251
  workspaceDependencies.push(subproject)
274
- // temporarily set cds.root to the dependency to resolve transitive deps thereof
275
- const cdsr = cds.root
276
- cds.root = subproject.path
277
- workspaceDependencies.push(...(await getWorkspaces(wsRoot, resolve, devDependencies, visited)))
278
- cds.root = cdsr
252
+ // recursively resolve transitive deps with the subproject as root
253
+ workspaceDependencies.push(...(await getWorkspaces(wsRoot, resolve, devDependencies, visited, subproject.path)))
279
254
  }
280
255
  }
281
256
  }
@@ -398,22 +373,21 @@ function _validateWsDependencies(wsRoot, dependencies, devDependencies) {
398
373
 
399
374
  /**
400
375
  * Returns a flat array of existing files and folders, trailing '/' or '\\' characters have been removed.
401
- * @param {NestedStringArray} modelPaths array of files and folders - optionally nested
402
- * @param {((m: string) => boolean) | undefined} filter filter function to apply to the found files
403
- * @returns
376
+ * @param {NestedStringArray | object | undefined } modelPaths array of files and folders - optionally nested
377
+ * @returns {string[] } flat array of existing files and folders
404
378
  */
405
- function _filterModelPaths(modelPaths, filter = undefined) {
379
+ function _filterModelPaths(modelPaths, { env = cds.env, root = cds.root } = {}) {
406
380
  const model = /** @type {Set<string>}*/(new Set())
407
381
  // may contain nested arrays
408
- const flatModelPaths = flatten(modelPaths)
409
- const { roots } = cds.env
382
+ const flatModelPaths = Array.isArray(modelPaths) ? modelPaths.flat(FLATTEN_MAX_DEPTH) : modelPaths ? [modelPaths] : []
383
+ const { roots } = env
410
384
  flatModelPaths.forEach(m => {
411
385
  if (m && !model.has(m) && !model.has(m + "/")) {
412
386
  // filter roots and absolute model paths that do not exist
413
387
  if (roots.includes(m)) {
414
- const dir = path.resolve(cds.root, m)
415
- // dir + FILE_EXT_CDS: might be cds file name, compatibility to old build configs
416
- if (fs.existsSync(dir) || fs.existsSync(dir + FILE_EXT_CDS)) {
388
+ const dirent = path.resolve(root, m)
389
+ // might be cds file name, compatibility to old build configs
390
+ if (exists(dirent) || exists(dirent + FILE_EXT_CDS) || exists(dirent.replaceAll('*', ''))) {
417
391
  model.add(m)
418
392
  }
419
393
  } else {
@@ -422,41 +396,9 @@ function _filterModelPaths(modelPaths, filter = undefined) {
422
396
  }
423
397
  })
424
398
  return [...model]
425
- .filter(m => filter ? filter(m) : true)
426
399
  .map(m => normalizePath(m))
427
400
  }
428
401
 
429
- /**
430
- * Returns a set of files found in the given directory and its subdirectories.
431
- * @param {string} srcDir - the directory to search for files
432
- * @param {(file: string) => boolean} [filter] - the filter function to apply to the found files
433
- * @param {string[]} files - the set of files to add the found files to
434
- * @param {boolean} [ignoreSymlinks=false] - whether to ignore symlinks
435
- * @returns {string[]} files found in the given directory and its subdirectories
436
- */
437
- function getFiles(srcDir, filter, files = [], ignoreSymlinks = false) {
438
- function handleResource(/** @type {string} entry */ entry) {
439
- if (!filter || filter.call(this, entry)) {
440
- const stats = fs.lstatSync(entry)
441
- if (cds.utils.isdir(entry)) {
442
- getFiles(entry, filter, files)
443
- } else if (stats.isFile() || (!ignoreSymlinks && stats.isSymbolicLink())) {
444
- files.push(entry)
445
- }
446
- }
447
- }
448
- let entries = []
449
- try {
450
- entries = fs.readdirSync(srcDir)
451
- } catch {
452
- // ignore if not existing
453
- }
454
- entries.map(subDirEntry => path.join(srcDir, subDirEntry)).forEach((entry) => {
455
- handleResource(entry)
456
- })
457
- return files
458
- }
459
-
460
402
  /**
461
403
  * Return gnu-style error string for location `loc`:
462
404
  * - 'File:Line:Col' without `loc.end`
@@ -494,7 +436,7 @@ class BuildMessage {
494
436
  * @param {string} message The message text
495
437
  * @param {import('./constants').Severity} [severity='Error'] severity - severity of the message
496
438
  * @param {SourceLocation | undefined} location Location of the message
497
- *
439
+ *
498
440
  * @memberOf BuildMessage
499
441
  */
500
442
  constructor(message, severity = SEVERITY_ERROR, location = undefined) {
@@ -521,7 +463,7 @@ class BuildError extends Error {
521
463
  /** @type {string[] | {message: string}[]} */
522
464
  messages
523
465
 
524
- /**
466
+ /**
525
467
  * @param {string} message
526
468
  * @param {string | string[]} messages
527
469
  */
@@ -542,22 +484,17 @@ class BuildError extends Error {
542
484
  }
543
485
 
544
486
  module.exports = {
545
- getProperty,
546
- setProperty,
547
487
  hasJavaNature,
548
488
  hasOptionValue,
549
489
  relativePaths,
550
490
  resolveRequiredSapModels,
551
- getDefaultModelOptions,
552
- flatten,
491
+ getDefaultModelPaths,
553
492
  getI18nDefaultFolder,
554
493
  normalizePath,
555
- pathExists,
556
494
  getWorkspacePaths,
557
495
  getWorkspaces,
558
496
  findWorkspaceRoot,
559
497
  filterFeaturePaths,
560
- getFiles,
561
498
  BuildMessage,
562
499
  BuildError
563
500
  }