@sap/cds-dk 9.6.0 → 9.7.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 (87) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/bin/add.js +5 -2
  3. package/bin/cds.js +11 -26
  4. package/bin/compile/index.js +3 -2
  5. package/bin/data-products/import.js +3 -0
  6. package/bin/debug.js +76 -16
  7. package/bin/env.js +2 -1
  8. package/bin/export.js +18 -13
  9. package/bin/help/export.md +2 -1
  10. package/bin/import.js +21 -18
  11. package/bin/init.js +8 -1
  12. package/bin/minify.js +11 -12
  13. package/bin/mock.js +17 -9
  14. package/bin/repl.js +2 -2
  15. package/bin/up.js +34 -4
  16. package/bin/version.js +202 -131
  17. package/lib/build/buildTaskEngine.js +4 -0
  18. package/lib/build/buildTaskFactory.js +5 -35
  19. package/lib/build/index.js +10 -10
  20. package/lib/build/plugin.js +6 -3
  21. package/lib/build/plugins/helm/files/subcharts/content-deployment/Chart.yaml +3 -1
  22. package/lib/build/plugins/helm/files/subcharts/content-deployment/templates/_helpers.tpl +1 -1
  23. package/lib/build/plugins/helm/files/subcharts/content-deployment/templates/job.yaml +8 -0
  24. package/lib/build/plugins/helm/files/subcharts/content-deployment/values.schema.json +18 -0
  25. package/lib/build/plugins/helm/files/subcharts/content-deployment/values.yaml +11 -1
  26. package/lib/build/plugins.js +8 -16
  27. package/lib/build/provider/hana/index.js +16 -19
  28. package/lib/build/provider/internalBuildPlugin.js +28 -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 -87
  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 +44 -41
  39. package/lib/init/merge.js +7 -2
  40. package/lib/init/projectReader.js +4 -10
  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/data/index.js +1 -1
  44. package/lib/init/template/extension/index.js +4 -4
  45. package/lib/init/template/hana/files/package.json.hbs +3 -3
  46. package/lib/init/template/handler/index.js +1 -1
  47. package/lib/init/template/helm/index.js +3 -3
  48. package/lib/init/template/helm-unified-runtime/index.js +5 -1
  49. package/lib/init/template/html5-repo/files/mta.yaml.hbs +1 -1
  50. package/lib/init/template/ias/files/mta.yaml.hbs +3 -3
  51. package/lib/init/template/initial/files/.vscode/tasks.json +10 -0
  52. package/lib/init/template/{nodejs/files/README.md → initial/files/readme.md} +2 -3
  53. package/lib/init/template/initial/index.js +15 -0
  54. package/lib/init/template/java/index.js +4 -11
  55. package/lib/init/template/kyma/index.js +21 -20
  56. package/lib/init/template/lint/index.js +3 -1
  57. package/lib/init/template/mta/index.js +0 -5
  58. package/lib/init/template/multitenancy/files/package.sidecar.json.hbs +1 -2
  59. package/lib/init/template/multitenancy/files/values.yaml.hbs +30 -0
  60. package/lib/init/template/nodejs/files/.vscode/tasks.json +1 -14
  61. package/lib/init/template/nodejs/files/package.json.hbs +2 -4
  62. package/lib/init/template/nodejs/index.js +8 -12
  63. package/lib/init/template/sample/files/app/common.cds +1 -1
  64. package/lib/init/template/sample/index.js +5 -6
  65. package/lib/init/template/sqlite/files/package.json.hbs +7 -1
  66. package/lib/init/template/test/index.js +1 -1
  67. package/lib/init/template/tiny-sample/files/nodejs/db/data/CatalogService.Books.csv +6 -0
  68. package/lib/init/template/tiny-sample/files/nodejs/srv/cat-service.cds +5 -0
  69. package/lib/init/template/tiny-sample/index.js +2 -2
  70. package/lib/init/template/workzone-standard/files/mta.yaml.hbs +6 -0
  71. package/lib/init/template/workzone-standard/index.js +13 -3
  72. package/lib/init/validate.js +11 -6
  73. package/lib/util/checksum.js +2 -2
  74. package/lib/util/fs.js +109 -53
  75. package/lib/util/glob.js +2 -1
  76. package/lib/util/term.js +4 -8
  77. package/lib/util/write.js +2 -4
  78. package/lib/watch/livereload-server.js +4 -3
  79. package/lib/watch/node-watch.js +5 -8
  80. package/lib/watch/watched.js +1 -1
  81. package/lib/watch/watcher.js +136 -0
  82. package/npm-shrinkwrap.json +245 -284
  83. package/package.json +2 -3
  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,6 +15,7 @@ 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 */
@@ -92,7 +94,7 @@ function relativePaths(root, qualifiedPaths) {
92
94
  * Returns a list of fully qualified model names belonging to the '@sap' namespace that cannot be resolved.
93
95
  * E.g. the module might NOT have been installed.
94
96
  * @param {string[]} modelPaths
95
- * @returns {string[]}
97
+ * @returns {boolean | string[]}
96
98
  */
97
99
  function resolveRequiredSapModels(modelPaths) {
98
100
  return Array.isArray(modelPaths) && modelPaths.filter(p => {
@@ -104,14 +106,35 @@ function resolveRequiredSapModels(modelPaths) {
104
106
  }
105
107
 
106
108
  /**
107
- * Returns the default model options for the given workspace.
109
+ * Returns the default model paths for the given workspace.
108
110
  * @param {boolean} ws - whether to include workspace models
109
- * @param {Function | undefined} filter - filter function for model paths
111
+ * @returns {Promise<string[]>} array of model paths
110
112
  */
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)
113
+ async function getDefaultModelPaths(ws = false) {
114
+ const { toggles } = cds.env.requires, { folders } = cds.env.features
115
+ const wildcards = toggles && folders ? ['*', folders] : '*'
116
+ if (!cds.resolve.locations) return _compatGetDefaultModelPaths(ws, wildcards)
117
+
118
+ let paths = _filterModelPaths(cds.resolve.locations(wildcards))
119
+ if (ws) {
120
+ const workspaces = (await getWorkspaces())
121
+ .map(ws => ws.workspace)
122
+ workspaces.forEach(workspace => {
123
+ const root = path.join(cds.root, workspace)
124
+ const env = cds.env.for('cds', root)
125
+ const resolved = cds.resolve.locations(env.requires.toggles ? wildcards : '*', { env, root })
126
+ paths.push(..._filterModelPaths(resolved, { root, env })
127
+ .map(p => normalizePath(path.join(workspace, p))))
128
+ })
129
+ // exclude redundant built-in models starting with workspace path, e.g. 'books/@sap/cds-mtxs/srv/bootstrap'
130
+ paths = paths.filter(p => NODEJS_MODEL_EXCLUDE_LIST.includes(p) || !NODEJS_MODEL_EXCLUDE_LIST.some(ex => p.endsWith(ex)))
131
+ }
132
+ return paths
133
+ }
134
+
135
+ // cds10: remove
136
+ async function _compatGetDefaultModelPaths(ws, wildcards) {
137
+ let paths = _filterModelPaths(cds.resolve(wildcards, false))
115
138
 
116
139
  if (ws) {
117
140
  const workspaces = (await getWorkspaces())
@@ -122,15 +145,15 @@ async function getDefaultModelOptions(ws = false, filter = undefined) {
122
145
  workspaces.forEach(workspace => {
123
146
  cds.root = path.join(root, workspace)
124
147
  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))))
148
+ const fromWorkspace = _filterModelPaths(cds.resolve(cds.env.requires.toggles ? wildcards : '*', false))
149
+ paths.push(...fromWorkspace.map(p => normalizePath(path.join(workspace, p))))
126
150
  })
127
151
  } finally {
128
152
  cds.root = root
129
153
  cds.env = env
130
154
  }
131
155
  // 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))))
156
+ paths = paths.filter(p => NODEJS_MODEL_EXCLUDE_LIST.includes(p) || !NODEJS_MODEL_EXCLUDE_LIST.some(ex => p.endsWith(ex)))
134
157
  }
135
158
  return paths
136
159
  }
@@ -143,7 +166,7 @@ async function getDefaultModelOptions(ws = false, filter = undefined) {
143
166
  /**
144
167
  * @overload
145
168
  * @param {unknown} dir
146
- * @returns {unknown}
169
+ * @returns {unknown}
147
170
  */
148
171
  /**
149
172
  * For valid paths replace '\\' with '/' and remove trailing '/'. Otherwise return as is
@@ -154,21 +177,6 @@ function normalizePath(dir) {
154
177
  return typeof dir === "string" ? dir.replace(/\\/g, '/').replace(/\/$/, '') : dir
155
178
  }
156
179
 
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
180
  function getI18nDefaultFolder() {
173
181
  // keep '_i18n' as preferred folder for compatibility reasons
174
182
  // if (cds.env.i18n.folders.includes('_i18n')) return '_i18n'
@@ -176,10 +184,6 @@ function getI18nDefaultFolder() {
176
184
  return cds.env.i18n.folders?.[0] ?? '_i18n'
177
185
  }
178
186
 
179
- async function pathExists(path) {
180
- return fs.promises.access(path).then(() => true).catch(() => false);
181
- }
182
-
183
187
  async function getWorkspacePaths() {
184
188
  const wsRoot = findWorkspaceRoot(cds.root)
185
189
  if (!wsRoot) {
@@ -246,15 +250,15 @@ async function getWorkspacesDeclarations (root) {
246
250
  * needs to be defined resulting in a corresponding symlink.
247
251
  * @returns {Promise<Workspace[]>} the paths are relative to 'wsRoot'.
248
252
  */
249
- async function getWorkspaces(wsRoot, resolve = false, devDependencies = true, visited = new Set()) {
250
- wsRoot ??= findWorkspaceRoot(cds.root)
253
+ async function getWorkspaces(wsRoot, resolve = false, devDependencies = true, visited = new Set(), root = cds.root) {
254
+ wsRoot ??= findWorkspaceRoot(root)
251
255
  if (!wsRoot) {
252
256
  return []
253
257
  }
254
258
  const pkgJsonWsRoot = require(path.join(wsRoot, 'package.json'))
255
259
  let pkgJson = pkgJsonWsRoot
256
- if (wsRoot !== cds.root) {
257
- pkgJson = require(path.join(cds.root, 'package.json'))
260
+ if (wsRoot !== root) {
261
+ pkgJson = require(path.join(root, 'package.json'))
258
262
  }
259
263
  const workspaceDependencies = []
260
264
  if (pkgJsonWsRoot.workspaces) {
@@ -271,11 +275,8 @@ async function getWorkspaces(wsRoot, resolve = false, devDependencies = true, vi
271
275
  if (subproject && !visited.has(pkg)) {
272
276
  visited.add(pkg)
273
277
  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
278
+ // recursively resolve transitive deps with the subproject as root
279
+ workspaceDependencies.push(...(await getWorkspaces(wsRoot, resolve, devDependencies, visited, subproject.path)))
279
280
  }
280
281
  }
281
282
  }
@@ -398,22 +399,21 @@ function _validateWsDependencies(wsRoot, dependencies, devDependencies) {
398
399
 
399
400
  /**
400
401
  * 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
402
+ * @param {NestedStringArray | object | undefined } modelPaths array of files and folders - optionally nested
403
+ * @returns {string[] } flat array of existing files and folders
404
404
  */
405
- function _filterModelPaths(modelPaths, filter = undefined) {
405
+ function _filterModelPaths(modelPaths, { env = cds.env, root = cds.root } = {}) {
406
406
  const model = /** @type {Set<string>}*/(new Set())
407
407
  // may contain nested arrays
408
- const flatModelPaths = flatten(modelPaths)
409
- const { roots } = cds.env
408
+ const flatModelPaths = Array.isArray(modelPaths) ? modelPaths.flat(FLATTEN_MAX_DEPTH) : modelPaths ? [modelPaths] : []
409
+ const { roots } = env
410
410
  flatModelPaths.forEach(m => {
411
411
  if (m && !model.has(m) && !model.has(m + "/")) {
412
412
  // filter roots and absolute model paths that do not exist
413
413
  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)) {
414
+ const dirent = path.resolve(root, m)
415
+ // might be cds file name, compatibility to old build configs
416
+ if (exists(dirent) || exists(dirent + FILE_EXT_CDS) || exists(dirent.replaceAll('*', ''))) {
417
417
  model.add(m)
418
418
  }
419
419
  } else {
@@ -422,41 +422,9 @@ function _filterModelPaths(modelPaths, filter = undefined) {
422
422
  }
423
423
  })
424
424
  return [...model]
425
- .filter(m => filter ? filter(m) : true)
426
425
  .map(m => normalizePath(m))
427
426
  }
428
427
 
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
428
  /**
461
429
  * Return gnu-style error string for location `loc`:
462
430
  * - 'File:Line:Col' without `loc.end`
@@ -494,7 +462,7 @@ class BuildMessage {
494
462
  * @param {string} message The message text
495
463
  * @param {import('./constants').Severity} [severity='Error'] severity - severity of the message
496
464
  * @param {SourceLocation | undefined} location Location of the message
497
- *
465
+ *
498
466
  * @memberOf BuildMessage
499
467
  */
500
468
  constructor(message, severity = SEVERITY_ERROR, location = undefined) {
@@ -521,7 +489,7 @@ class BuildError extends Error {
521
489
  /** @type {string[] | {message: string}[]} */
522
490
  messages
523
491
 
524
- /**
492
+ /**
525
493
  * @param {string} message
526
494
  * @param {string | string[]} messages
527
495
  */
@@ -548,16 +516,13 @@ module.exports = {
548
516
  hasOptionValue,
549
517
  relativePaths,
550
518
  resolveRequiredSapModels,
551
- getDefaultModelOptions,
552
- flatten,
519
+ getDefaultModelPaths,
553
520
  getI18nDefaultFolder,
554
521
  normalizePath,
555
- pathExists,
556
522
  getWorkspacePaths,
557
523
  getWorkspaces,
558
524
  findWorkspaceRoot,
559
525
  filterFeaturePaths,
560
- getFiles,
561
526
  BuildMessage,
562
527
  BuildError
563
528
  }