@sap/cds-dk 7.6.1 → 7.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 (49) hide show
  1. package/bin/cds.js +1 -1
  2. package/bin/version.js +21 -20
  3. package/bin/watch.js +6 -0
  4. package/lib/build/buildTaskEngine.js +6 -4
  5. package/lib/build/buildTaskProviderFactory.js +5 -0
  6. package/lib/build/provider/edmxBuildPlugin.js +1 -1
  7. package/lib/build/provider/internalBuildTaskProvider.js +12 -10
  8. package/lib/build/provider/mtx/index.js +2 -2
  9. package/lib/build/provider/mtx/resourcesTarBuilder.js +1 -1
  10. package/lib/build/provider/mtx-extension/extensionCompilation.js +50 -0
  11. package/lib/build/provider/mtx-extension/index.js +70 -50
  12. package/lib/build/provider/mtx-sidecar/index.js +3 -3
  13. package/lib/build/util.js +9 -4
  14. package/lib/env/schema.js +1 -0
  15. package/lib/env/schemas/cds-rc.js +54 -1
  16. package/lib/import/asyncapi/parserContext.js +6 -0
  17. package/lib/init/add.js +2 -0
  18. package/lib/init/constants.js +2 -2
  19. package/lib/init/index.js +4 -8
  20. package/lib/init/merge.js +31 -13
  21. package/lib/init/projectReader.js +9 -1
  22. package/lib/init/registries/mta.js +19 -1
  23. package/lib/init/template/approuter/files/mta.yaml.hbs +1 -1
  24. package/lib/init/template/approuter/index.js +5 -5
  25. package/lib/init/template/connectivity/index.js +3 -4
  26. package/lib/init/template/helm/files/content-deployment/Chart.yaml +1 -1
  27. package/lib/init/template/helm/files/content-deployment/templates/job.yaml +3 -0
  28. package/lib/init/template/helm/files/content-deployment/values.schema.json +13 -1
  29. package/lib/init/template/helm/files/content-deployment/values.yaml +1 -1
  30. package/lib/init/template/helm/files/web-application/Chart.yaml +1 -1
  31. package/lib/init/template/helm/files/web-application/templates/deployment.yaml +1 -0
  32. package/lib/init/template/helm/files/web-application/templates/service.yaml +4 -0
  33. package/lib/init/template/helm/files/web-application/values.schema.json +16 -0
  34. package/lib/init/template/helm/files/web-application/values.yaml +7 -1
  35. package/lib/init/template/html5-repo/files/app-package.json.hbs +16 -0
  36. package/lib/init/template/html5-repo/files/mta.yaml.hbs +83 -0
  37. package/lib/init/template/html5-repo/files/package.json.hbs +17 -0
  38. package/lib/init/template/html5-repo/files/ui5.yaml.hbs +33 -0
  39. package/lib/init/template/html5-repo/files/xs-app.json.hbs +19 -0
  40. package/lib/init/template/html5-repo/index.js +56 -12
  41. package/lib/init/template/mta/files/mta.yaml.hbs +1 -0
  42. package/lib/init/template/mta/index.js +5 -0
  43. package/lib/init/template/nodejs/files/.vscode/extensions.json +1 -2
  44. package/lib/init/template/nodejs/files/README.md +1 -1
  45. package/lib/init/template/sample/files/app/index.html +8 -6
  46. package/lib/init/template/sample/index.js +12 -2
  47. package/lib/util/cf.js +1 -0
  48. package/npm-shrinkwrap.json +31 -29
  49. package/package.json +1 -1
package/bin/cds.js CHANGED
@@ -77,7 +77,7 @@ const cli = {
77
77
  args (task, argv) {
78
78
 
79
79
  const { options:o=[], flags:f=[], shortcuts:s=[] } = task
80
- const _global = /^--(profile|production|sql|odata|build-.*|cdsc-.*|odata-.*|folders-.*)$/
80
+ const _global = /^--(profile|production|sql|odata|add-.*|build-.*|cdsc-.*|odata-.*|folders-.*)$/
81
81
  const _flags = { '--production':true }
82
82
  const options = {}, args = []
83
83
  let k,a, env = null
package/bin/version.js CHANGED
@@ -89,30 +89,31 @@ function _versions_from(pkg_path, pkg_label) {
89
89
  }
90
90
 
91
91
  function _markdown (versions) {
92
- console.log()
93
92
  const pkg = { name:'', repository:'', version:'' }; try {
94
93
  Object.assign (pkg, require (resolve('package.json')))
95
94
  } catch (e) {/* ignored */}
96
- console.log ('|', pkg.name, '|', pkg.repository.url || pkg.repository || 'https://github.com/<your/repo>', '|')
97
- console.log ('|:---------------------- | ----------- |')
98
- if (require('../lib').env['project-nature'] === 'nodejs') {
99
- console.log ('|', v('Node.js'), '|')
100
- console.log ('|', v('@sap/cds'), '|')
101
- } else {
102
- console.log ('|', v('CAP Java Runtime'), '|')
103
- }
104
- console.log ('|', v('@sap/cds-compiler'), '|')
105
- console.log ('|', v('@sap/cds-dk'), '|')
106
- console.log ('|', v('@sap/cds-dk (global)'), '|')
107
- console.log ('|', v('@sap/eslint-plugin-cds'), '|')
108
- console.log ('|', v('@sap/cds-mtxs'), '|')
109
- console.log ('|', v('@cap-js/cds-types'), '|')
110
- function v (component) {
111
- const version = versions [component] || MISSING
112
- return (component + ' ').slice(0,22)
113
- +' | '+ (version + ' ').slice(0,11)
114
- }
95
+ const repo = pkg.repository.url || pkg.repository || 'https://github.com/<your/repo>'
96
+ const valLength = Math.max(repo.length, versions.home.length)
97
+
98
+ console.log ()
99
+ console.log ('|', $(pkg.name, repo), '|')
100
+ console.log ('|', $('----------------------', '-'.repeat(valLength)), '|')
101
+
102
+ Object.keys(versions)
103
+ .filter(mod => mod != pkg.name)
104
+ .sort()
105
+ .forEach(id => console.log ('|', $(id, versions[id]), '|'))
106
+
107
+ // TODO get CAP Java version from mvn
108
+ // if (require('../lib').env['project-nature'] === 'java') {
109
+ // console.log ('|', $('CAP Java Runtime'), '|')
110
+ // }
115
111
  console.log()
112
+
113
+ function $ (key, val=MISSING) {
114
+ return ( key + ' '.repeat(22)).slice(0,22)
115
+ +' | '+ (val + ' '.repeat(valLength)).slice(0,valLength)
116
+ }
116
117
  }
117
118
 
118
119
  function _pkg_path(id, ...paths) {
package/bin/watch.js CHANGED
@@ -78,6 +78,12 @@ async function watch ([cwd], {
78
78
  ext = extDefaults,
79
79
  ...options
80
80
  }={}) {
81
+ if (cwd && !cds.utils.exists(cwd)) try { // try module name like `@capire/bookshop`
82
+ cwd = require.resolve(cwd+'/package.json').slice(0,-13)
83
+ } catch (err) {
84
+ throw `Error: No such folder or package: '${cwd}'`
85
+ }
86
+
81
87
  if (!Object.keys(options).includes('with-mocks')) args.push ('--with-mocks')
82
88
  if (!Object.keys(options).find(a => /^in-memory\??$/.test(a))) args.push ('--in-memory?')
83
89
  args
@@ -9,6 +9,7 @@ const InternalBuildPlugin = require('./provider/internalBuildPlugin')
9
9
  const COMPILATION_ERROR = 'CompilationError'
10
10
  const COMPILE_MESSAGE = 'CompileMessage'
11
11
  const DEBUG = cds.debug('cli|build')
12
+ const LOG_LIMIT = 30
12
13
 
13
14
  class BuildTaskEngine {
14
15
  constructor(options) {
@@ -38,7 +39,7 @@ class BuildTaskEngine {
38
39
  // validate required @sap namespace models - log only
39
40
  const unresolved = BuildTaskEngine._resolveRequiredSapServices(tasks)
40
41
  if (unresolved.length > 0) {
41
- throw new BuildError(`Required CDS models [${unresolved.join(', ')}] cannot be resolved. Make sure up-to-date versions of the missing modules are installed.`)
42
+ throw new BuildError(`Required CDS models [${unresolved.join(', ')}] cannot be resolved. Use the 'npm install' command to install up-to-date versions of the missing packages.`)
42
43
  }
43
44
 
44
45
  // create build plugins
@@ -221,9 +222,10 @@ class BuildTaskEngine {
221
222
  const files = BuildTaskEngine._getOutput(plugins)
222
223
  if (files.length > 0) {
223
224
  const term = require('../util/term')
224
- if (!process.env.DEBUG && files.length > 20) {
225
- files.length = 20
226
- files.push('...')
225
+ if (!process.env.DEBUG && files.length > LOG_LIMIT) {
226
+ const length = files.length
227
+ files.length = LOG_LIMIT
228
+ files.push(`... ${length - files.length} more. Run with DEBUG=build to show all files.`)
227
229
  }
228
230
  console.log(`done > wrote output to:\n ${term.secondary(files.join("\n "))}\n`)
229
231
  }
@@ -13,6 +13,11 @@ class BuildTaskProviderFactory {
13
13
  if (options.clean === undefined) {
14
14
  options.clean = true
15
15
  }
16
+ ['for', 'src', 'dest'].forEach(key => {
17
+ if (options[key] && typeof options[key] !== 'string') {
18
+ throw new BuildError(`Invalid build options - property '${key}' must be a string`)
19
+ }
20
+ })
16
21
  this._buildOptions = options
17
22
  this._context = { options, tasks: [] } // resolved tasks
18
23
  }
@@ -52,7 +52,7 @@ class EdmxBuildPlugin extends FeatureToggleBuildPlugin {
52
52
  }
53
53
 
54
54
  _isOdataProtocol(srv) {
55
- if ('_serves_odata' in srv) return srv._serves_odata // >= cds 7.4.0
55
+ let yes = srv._serves_odata; if (yes) return typeof yes === 'function' ? yes.call(srv) : yes // >= cds 7.4.0
56
56
  let protocols = cds.service.protocols4?.(srv) // >= cds 7.4.0
57
57
  if (!protocols) { // @sap/cds < 7.3.0
58
58
  protocols = cds.service.protocols.protocol4(srv)
@@ -183,7 +183,7 @@ Upgrade to @sap/cds-mtxs following the migration guide https://cap.cloud.sap/doc
183
183
  if (!sidecarEnv.requires["cds.xt.ModelProviderService"]?._in_sidecar) {
184
184
  if (sidecarEnv.profiles?.includes('mtx-sidecar')) {
185
185
  // profile has been set, but cannot be resolved
186
- throw new BuildError('MTX configuration cannot be resolved. Make sure up-to-date versions of @sap/cds-mtxs and @sap/cds are installed.')
186
+ throw new BuildError("MTX configuration cannot be resolved. Use the 'npm install' command to install up-to-date versions of @sap/cds-mtxs and @sap/cds.")
187
187
  }
188
188
  throw new BuildError(`Invalid MTX sidecar configuration - profile 'mtx-sidecar' not set.`)
189
189
  }
@@ -264,26 +264,29 @@ Upgrade to @sap/cds-mtxs following the migration guide https://cap.cloud.sap/doc
264
264
  static _setDefaultSrcFolder(task) {
265
265
  switch (task.for) {
266
266
  case BUILD_TASK_HANA:
267
- task.src = task.src || normalizePath(cds.env.folders.db)
267
+ task.src ||= normalizePath(cds.env.folders.db)
268
268
  break
269
269
  case BUILD_TASK_JAVA:
270
270
  case BUILD_TASK_JAVA_CF:
271
271
  case BUILD_TASK_NODEJS:
272
272
  case BUILD_TASK_NODE_CF:
273
- task.src = task.src || normalizePath(cds.env.folders.srv)
273
+ task.src ||= normalizePath(cds.env.folders.srv)
274
274
  break
275
275
  case BUILD_TASK_FIORI:
276
- task.src = task.src || normalizePath(cds.env.folders.app)
276
+ task.src ||= normalizePath(cds.env.folders.app)
277
277
  break
278
278
  case BUILD_TASK_MTX_SIDECAR:
279
- task.src = task.src || MTX_SIDECAR_FOLDER
279
+ task.src ||= MTX_SIDECAR_FOLDER
280
280
  break
281
281
  case BUILD_TASK_MTX_EXTENSION:
282
- task.src = task.src || "."
282
+ if (task.src && task.src !== cds.root) {
283
+ throw new BuildError("Invalid 'src' property value for build task 'mtx-extension', the only allowed value is '.'")
284
+ }
285
+ task.src ||= "."
283
286
  break
284
287
  case BUILD_TASK_MTX:
285
288
  // mtxs with nodejs, but without sidecar
286
- task.src = task.src || normalizePath(cds.env.folders.srv)
289
+ task.src ||= normalizePath(cds.env.folders.srv)
287
290
  break
288
291
  default:
289
292
  throw new Error(`Unknown build task '${task.use || task.for}'`)
@@ -296,8 +299,7 @@ Upgrade to @sap/cds-mtxs following the migration guide https://cap.cloud.sap/doc
296
299
  if (taskModelPaths && !Array.isArray(taskModelPaths)) {
297
300
  taskModelPaths = [taskModelPaths]
298
301
  }
299
- const toggles = cds.env.features.folders
300
- const allowList = new RegExp(`^@sap/cds${toggles ? '|^' + toggles.replace('*', '') : ''}`)
302
+ const allowList = new RegExp(`^@sap/cds${cds.env.requires.toggles ? '|^' + cds.env.features.folders.replace('*', '') : ''}`)
301
303
  task.options = task.options || {}
302
304
  let defaultModelPaths = []
303
305
  if (taskModelPaths?.length) {
@@ -336,7 +338,7 @@ Upgrade to @sap/cds-mtxs following the migration guide https://cap.cloud.sap/doc
336
338
  }
337
339
 
338
340
  /**
339
- * Determines the module folder from the past list that may represent files or folders w or w/o .cds file extension.
341
+ * Determines the module folder from the given list that may represent files or folders w or w/o .cds file extension.
340
342
  * @param {Array} filesOrFolders
341
343
  */
342
344
  static _getModuleFolder(filesOrFolders) {
@@ -3,7 +3,7 @@ const fs = require('fs')
3
3
  const path = require('path')
4
4
  const cds = require('../../../cds')
5
5
  const EdmxBuildPlugin = require('../edmxBuildPlugin')
6
- const ResourcesTarProvider = require('./resourcesTarBuilder')
6
+ const ResourcesTarBuilder = require('./resourcesTarBuilder')
7
7
  const { FOLDER_GEN } = require('../../constants')
8
8
  const { BuildError } = require('../../util')
9
9
 
@@ -29,7 +29,7 @@ Upgrade to @sap/cds-mtxs following the migration guide https://cap.cloud.sap/doc
29
29
  if (!model) {
30
30
  return
31
31
  }
32
- return new ResourcesTarProvider(this).createTar(dest, model)
32
+ return new ResourcesTarBuilder(this).createTar(dest, model)
33
33
  }
34
34
 
35
35
  async clean() {
@@ -34,7 +34,7 @@ class ResourcesTarBuilder {
34
34
  * If omitted all resources contained in the root directory are added to the TAR file.
35
35
  */
36
36
  async writeTarFile(tarFile, root, resources) {
37
- const { tar } = require('../../../../lib').utils
37
+ const { tar } = require('../../..').utils
38
38
  await tar.czfd(tarFile, root, resources)
39
39
  this.plugin.pushFile(tarFile)
40
40
  }
@@ -0,0 +1,50 @@
1
+ const path = require('path')
2
+ const cds = require('../../../cds')
3
+ const { WARNING } = require('../internalBuildPlugin')
4
+
5
+ class ExtensionCompilation {
6
+ constructor(plugin) {
7
+ this._plugin = plugin
8
+ }
9
+
10
+ get plugin() { return this._plugin }
11
+
12
+ isDuplicateDefinitionError(e) {
13
+ return e.code === 'ERR_CDS_COMPILATION_FAILURE' && e.messages?.some(m => m.messageId === 'duplicate-definition')
14
+ }
15
+
16
+ async recompileWithoutDuplicates(messages, saasPkgPath) {
17
+ // determine directories containing duplicate CDS definitions
18
+ const duplicates = messages?.reduce((acc, m) => {
19
+ if (m.messageId === 'duplicate-definition' && m.$location?.file) {
20
+ const dirname = path.resolve(cds.root, m.$location.file)
21
+ if (!dirname.startsWith(saasPkgPath)) {
22
+ acc.add(path.dirname(dirname))
23
+ }
24
+ }
25
+ return acc
26
+ }, new Set())
27
+
28
+ function getPkgName(dirname) {
29
+ try {
30
+ if (dirname && dirname !== cds.root && path.dirname(dirname) !== dirname) {
31
+ const pkg = require(path.join(dirname, 'package.json'))
32
+ return pkg.name
33
+ }
34
+ } catch (e) {
35
+ return getPkgName(path.dirname(dirname))
36
+ }
37
+ }
38
+ const pkgNames = [...[...duplicates].reduce((acc, dir) => acc.add(getPkgName(dir)), new Set())]
39
+ const modelPaths = this.plugin.task.options.model.filter(m => !pkgNames.some(pkg => m === pkg || m.startsWith(pkg + '/')))
40
+ this.plugin.task.options.model = modelPaths
41
+ this.plugin.pushMessage(`Duplicate CDS model definitions found. Using CDS artifacts from base model instead and recompiling with model options ${JSON.stringify(modelPaths)}.\nMake sure to use compatible versions for the dependencies '${pkgNames.join(', ')}'.`, WARNING)
42
+ // recompile without duplicates
43
+ return this.plugin.model()
44
+ }
45
+
46
+ resolveExtensionFiles(model, saasPkgFolder) {
47
+ return model['$sources'].filter(file => file.startsWith(cds.root) && !file.startsWith(saasPkgFolder))
48
+ }
49
+ }
50
+ module.exports = ExtensionCompilation
@@ -2,8 +2,9 @@ const path = require('path')
2
2
  const fs = require('fs')
3
3
  const cds = require('../../../cds')
4
4
  const InternalBuildPlugin = require('../internalBuildPlugin')
5
- const { FOLDER_GEN, EXTENSION_POINT_VALIDATION } = require('../../constants')
6
5
  const ResourcesTarBuilder = require('../mtx/resourcesTarBuilder')
6
+ const ExtensionCompilation = require('./extensionCompilation')
7
+ const { FOLDER_GEN, EXTENSION_POINT_VALIDATION } = require('../../constants')
7
8
  const { BuildError, BuildMessage } = require('../../util')
8
9
  const DEBUG = cds.debug('cli|build')
9
10
 
@@ -16,69 +17,73 @@ class MtxExtensionBuildPlugin extends InternalBuildPlugin {
16
17
  }
17
18
 
18
19
  async build() {
19
- const { src, dest } = this.task
20
+ const { dest } = this.task
20
21
  const destExt = path.join(dest, 'ext')
21
- // check existence of appPackage folder only
22
- // REVISIT: cds.resolve will fail as no index.csn file exists in this folder for the extension migration use case
22
+ let model
23
+ let extensionModel
24
+
25
+ // REVISIT: check existence of SaaS app package folder only
26
+ // cds.resolve will fail as no index.csn file exists in this folder for the extension migration use case
23
27
  // a compilation error is thrown anyhow if any base model refs cannot be resolved
24
- const appPackageFolder = this._getAppPackageFolder()
25
- if (!fs.existsSync(path.join(src, appPackageFolder))) {
26
- throw new BuildError(`The SaaS application base model '${appPackageFolder}' is missing. Have you run the 'cds pull' command?`)
27
- }
28
+ const saasPkgPath = MtxExtensionBuildPlugin._getSaasPkgPath()
29
+ const extensionCompilation = new ExtensionCompilation(this)
28
30
 
29
31
  // full compile of the extension using base model - ensuring consistency
30
- const model = await this.model()
32
+ try {
33
+ model = await this.model()
34
+ } catch (e) {
35
+ if (extensionCompilation.isDuplicateDefinitionError(e)) {
36
+ // clear compilation errors and recompile with removed duplicate resources
37
+ this.messages.length = 0
38
+ model = await extensionCompilation.recompileWithoutDuplicates(e.messages, saasPkgPath)
39
+ }
40
+ }
31
41
 
32
42
  if (model) {
33
43
  // extension CSN using parsed format
34
44
  const options = { ...this.options(), flavor: 'parsed' }
35
- const extModel = await cds.load(this._resolveExtensionFiles(model), options)
36
- if (extModel.requires) {
37
- extModel.requires.length = 0
45
+ const extensionFiles = extensionCompilation.resolveExtensionFiles(model, saasPkgPath)
46
+ if (extensionFiles.length > 0) {
47
+ extensionModel = await cds.load(extensionFiles, options)
48
+ if (extensionModel.requires) {
49
+ extensionModel.requires.length = 0
50
+ }
38
51
  }
39
-
52
+ }
53
+ if (extensionModel) {
40
54
  // IMPORTANT NOTE: perform all model operations before the linter is called as the linter
41
55
  // will modify the model causing the creation of 'unresolved' associations
42
- await this.compileToJson(extModel, path.join(destExt, 'extension.csn'))
43
- await this.collectLanguageBundles(extModel, path.join(destExt, 'i18n'))
44
-
45
- // perform SQL backend validation to prevent extension deployment problems later on
46
- // REVISIT: disabled - causing stack overflow errors in LinkedCSN for some scenarios
47
- // cds.compile.to.sql(model)
56
+ await this.compileToJson(extensionModel, path.join(destExt, 'extension.csn'))
57
+ await this.collectLanguageBundles(extensionModel, path.join(destExt, 'i18n'))
48
58
 
49
59
  const files = Object.keys(await cds.deploy.resources(model))
50
60
  if (files.length > 0) {
51
61
  const dataDest = path.join(destExt, 'data')
52
62
  await Promise.all(
53
- files
54
- .filter(file => /\.csv$/.test(file))
55
- .map(csv => {
56
- return this.copy(csv).to(path.join(dataDest, path.basename(csv)))
57
- })
63
+ files.filter(file => /\.csv$/.test(file)).map(csv => {
64
+ return this.copy(csv).to(path.join(dataDest, path.basename(csv)))
65
+ })
58
66
  )
59
67
  }
60
68
 
61
69
  if (!this.hasBuildOption(EXTENSION_POINT_VALIDATION, false)) {
62
70
  try {
63
71
  // throws error in case of linting errors
64
- this._lintExtModel(extModel, model)
72
+ this._lintExtModel(extensionModel, model, saasPkgPath)
65
73
  } catch (error) {
66
74
  // cleanup existing files in case of an error
67
- await fs.promises.rm(this.task.dest, { recursive: true })
75
+ await fs.promises.rm(dest, { recursive: true })
68
76
  throw error
69
77
  }
70
78
  }
71
79
  }
72
80
 
73
- const packageJson = path.join(src, 'package.json')
74
- if (!fs.existsSync(packageJson)) {
75
- throw new BuildError(`package.json file not found in ${src}`)
76
- }
77
- await this.copy(packageJson).to(path.join(destExt, 'package.json'))
81
+ // existence already checked
82
+ await this.copy(path.join(cds.root, 'package.json')).to(path.join(destExt, 'package.json'))
78
83
 
79
84
  // copy handlers
80
- const folders = [path.join(src, cds.env.folders.srv, 'handlers')]
81
- await this.copyNativeContent(src, destExt, res => {
85
+ const folders = [path.join(cds.root, cds.env.folders.srv, 'handlers')]
86
+ await this.copyNativeContent(cds.root, destExt, res => {
82
87
  if (fs.statSync(res).isDirectory()) {
83
88
  return folders.some(folder => folder.startsWith(res))
84
89
  }
@@ -88,27 +93,15 @@ class MtxExtensionBuildPlugin extends InternalBuildPlugin {
88
93
  })
89
94
 
90
95
  // add all resources contained in 'gen/ext' folder
91
- await new ResourcesTarBuilder(this).writeTarFile(path.join(this.task.dest, 'extension.tgz'), destExt)
96
+ return new ResourcesTarBuilder(this).writeTarFile(path.join(dest, 'extension.tgz'), destExt)
92
97
  }
93
98
 
94
- _resolveExtensionFiles(model) {
95
- const node_modules = path.join(this.task.src, 'node_modules')
96
- const paths = model['$sources'].reduce((acc, file) => {
97
- if (file.startsWith(this.task.src) && !file.startsWith(node_modules)) {
98
- acc.push(file)
99
- }
100
- return acc
101
- }, [])
102
-
103
- return paths
104
- }
105
-
106
- _lintExtModel(extModel, model) {
99
+ _lintExtModel(extModel, model, saasPkgPath) {
107
100
  const linter = this._linter()
108
101
  if (!linter) {
109
102
  return
110
103
  }
111
- const env = cds.env.for('cds', path.join(this.task.src, this._getAppPackageFolder()))
104
+ const env = cds.env.for('cds', saasPkgPath)
112
105
  DEBUG?.(`Saas extension point restrictions:\n${env.requires?.['cds.xt.ExtensibilityService']}`)
113
106
 
114
107
  const messages = linter.lint(extModel, model, env)
@@ -135,8 +128,35 @@ class MtxExtensionBuildPlugin extends InternalBuildPlugin {
135
128
  return linter
136
129
  }
137
130
 
138
- _getAppPackageFolder() {
139
- return path.join('node_modules', cds.env.extends || '_base');
131
+ static _getSaasPkgPath() {
132
+ // cds.env.extends holds the SaaS app package name, also if the extends field is defined on root level
133
+ let saasPkgPath = this._getWorkspacePath(cds.env.extends || '_base')
134
+ if (!saasPkgPath) {
135
+ // older projects might not have a workspace definition
136
+ saasPkgPath = this._getNodeModulesPath(saasPkgPath)
137
+ }
138
+ return saasPkgPath
139
+ }
140
+
141
+ static _getWorkspacePath(pkg) {
142
+ const pkgJson = require(path.join(cds.root, 'package.json'))
143
+ if (pkgJson.workspaces) {
144
+ try {
145
+ // loading package.json of SaaS app - might not exist for older projects
146
+ return path.dirname(require.resolve(path.join(pkg, 'package.json'), { paths: [cds.root] }))
147
+ } catch (e) {
148
+ if (e.code !== 'MODULE_NOT_FOUND') throw e
149
+ throw new BuildError(`The SaaS application base model is missing. Did you execute 'cds pull' and 'npm install'?`)
150
+ }
151
+ }
152
+ }
153
+
154
+ static _getNodeModulesPath() {
155
+ const saasPkgPath = path.join(cds.root, 'node_modules', cds.env.extends || '_base')
156
+ if (!fs.existsSync(saasPkgPath)) {
157
+ throw new BuildError(`The SaaS application base model is missing. Did you execute 'cds pull' and 'npm install'?`)
158
+ }
159
+ return saasPkgPath
140
160
  }
141
161
  }
142
162
 
@@ -3,7 +3,7 @@ const cds = require('../../../cds')
3
3
  const fs = require('fs')
4
4
  const { FOLDER_GEN, DEFAULT_CSN_FILE_NAME, MTX_SIDECAR_DB_VALIDATION, CONTENT_EDMX, FLAVOR_LOCALIZED_EDMX } = require('../../constants')
5
5
  const NodejsBuildPlugin = require('../nodejs')
6
- const ResourcesTarProvider = require('../mtx/resourcesTarBuilder')
6
+ const ResourcesTarBuilder = require('../mtx/resourcesTarBuilder')
7
7
  const { ERROR } = NodejsBuildPlugin
8
8
  const { relativePaths, BuildError, resolveRequiredSapModels, getI18nDefaultFolder } = require('../../util')
9
9
  const DEBUG = cds.debug('cli|build')
@@ -64,7 +64,7 @@ class MtxSidecarBuildPlugin extends NodejsBuildPlugin {
64
64
  async _buildMainApp(sidecarEnv) {
65
65
  let main = sidecarEnv.requires['cds.xt.ModelProviderService']?.root
66
66
  if (!main) {
67
- throw new BuildError(`Invalid MTX sidecar configuration. Make sure that the profile 'mtx-sidecar' is configured and an up-to-date version of @sap/cds-mtxs is installed.`)
67
+ throw new BuildError("Invalid MTX sidecar configuration. Make sure that the profile 'mtx-sidecar' is configured. Use 'npm install @sap/cds-mtxs' to install an up-to-date version.")
68
68
  }
69
69
  if (!this.hasBuildOption(MTX_SIDECAR_DB_VALIDATION, false)
70
70
  && !cds.env.build.tasks?.length // disable if custom build tasks exist
@@ -97,7 +97,7 @@ class MtxSidecarBuildPlugin extends NodejsBuildPlugin {
97
97
 
98
98
  // create resources TAR
99
99
  // resources are determined based on available database build task, SQLite as fallback
100
- await new ResourcesTarProvider(this).createTar(destMain, model)
100
+ await new ResourcesTarBuilder(this).createTar(destMain, model)
101
101
 
102
102
  // copy package.json and .cdsrc.json from project root
103
103
  await this._copyMainConfigFiles(cds.root, destMain)
package/lib/build/util.js CHANGED
@@ -119,7 +119,7 @@ function resolveRequiredSapModels(modelPaths) {
119
119
 
120
120
  async function getDefaultModelOptions(ws = false) {
121
121
  const wildcards = cds.env.requires.toggles && cds.env.features.folders ? ['*', cds.env.features.folders] : '*'
122
- let paths = _pushModelPaths(cds.resolve(wildcards, false))
122
+ let paths = _filterModelPaths(cds.resolve(wildcards, false))
123
123
 
124
124
  if (ws) {
125
125
  const workspaces = await getWorkspaces()
@@ -129,7 +129,7 @@ async function getDefaultModelOptions(ws = false) {
129
129
  workspaces.forEach(workspace => {
130
130
  cds.root = path.join(root, workspace)
131
131
  cds.env = cds.env.for('cds', cds.root)
132
- paths = paths.concat(_pushModelPaths(cds.resolve(cds.env.requires.toggles ? wildcards : '*', false)).map(p => path.join(workspace, p)))
132
+ paths = paths.concat(_filterModelPaths(cds.resolve(cds.env.requires.toggles ? wildcards : '*', false)).map(p => path.join(workspace, p)))
133
133
  })
134
134
  } finally {
135
135
  cds.root = root
@@ -247,7 +247,7 @@ function _getWorkspace(wsRoot, pkg) {
247
247
  }
248
248
 
249
249
  function _validateWsDependencies(wsRoot, dependencies, devDependencies) {
250
- const allDependencies = {...dependencies, ...devDependencies}
250
+ const allDependencies = { ...dependencies, ...devDependencies }
251
251
  for (const name in allDependencies) {
252
252
  if (allDependencies[name] === '*') {
253
253
  try {
@@ -262,7 +262,12 @@ function _validateWsDependencies(wsRoot, dependencies, devDependencies) {
262
262
  }
263
263
  }
264
264
 
265
- function _pushModelPaths(...modelPaths) {
265
+ /**
266
+ * Returns a flat array of existing files and folders, trailing '/' or '\\' characters have been removed.
267
+ * @param {...any} modelPaths array of files and folders - optionally nested
268
+ * @returns
269
+ */
270
+ function _filterModelPaths(...modelPaths) {
266
271
  const model = new Set()
267
272
  // may contain nested arrays
268
273
  modelPaths = flatten(modelPaths)
package/lib/env/schema.js CHANGED
@@ -65,6 +65,7 @@ _robustRequire.resolve = function (id, options = {}) {
65
65
  }
66
66
 
67
67
  function mergePluginSchema(pluginSchema) {
68
+ // contribution points also appear in schema file ./schemas/cds-rc.js
68
69
  return {
69
70
  into: async (targetSchema) => {
70
71
  if (pluginSchema.buildTaskType) {
@@ -5,6 +5,59 @@ module.exports = {
5
5
  $defs: {
6
6
  cdsRoot: {
7
7
  properties: {
8
+ // declaration for schema node for cds plugins contributing cds configuration
9
+ schema: {
10
+ type: "object",
11
+ description: "Schema declaration for cds plugins contributing cds configuration",
12
+ additionalProperties: false,
13
+ properties: {
14
+ cds: {
15
+ type: "object",
16
+ description: "Containing json schema declarations for root level entries under cds node.",
17
+ patternProperties: {
18
+ // new keyword must start with word character or opening brackets
19
+ "^[\\[\\(\\w].*": {
20
+ $ref: "https://json-schema.org/draft/2020-12/schema"
21
+ }
22
+ }
23
+ },
24
+
25
+ buildTaskType: {
26
+ type: "object",
27
+ additionalProperties: false,
28
+ required: ["name"],
29
+ properties: {
30
+ name: {
31
+ type: "string",
32
+ minLength: 1,
33
+ description: "The name of the build task type."
34
+ },
35
+ description: {
36
+ type: "string",
37
+ description: "The description of the build task type."
38
+ }
39
+ }
40
+ },
41
+
42
+ databaseType: {
43
+ type: "object",
44
+ additionalProperties: false,
45
+ required: ["name"],
46
+ properties: {
47
+ name: {
48
+ type: "string",
49
+ minLength: 1,
50
+ description: "The name of the database type."
51
+ },
52
+ description: {
53
+ type: "string",
54
+ description: "The description of the database type."
55
+ }
56
+ }
57
+ }
58
+ }
59
+ },
60
+
8
61
  // build specific schema
9
62
  build: {
10
63
  type: "object",
@@ -115,4 +168,4 @@ module.exports = {
115
168
  ]
116
169
  }
117
170
  }
118
- }
171
+ }
@@ -319,6 +319,12 @@ module.exports = class ParserContext {
319
319
  */
320
320
  getPropertyObject(propertyContent) {
321
321
  const property = {};
322
+ if ('$ref' in propertyContent) {
323
+ const refPathParts = propertyContent['$ref'].split('/');
324
+ const propertyName = refPathParts[refPathParts.length - 1];
325
+ property['type'] = { 'ref': [refPathParts[3], propertyName] };
326
+ return property;
327
+ }
322
328
  const inputType = this.getTypeFromArray(propertyContent.type);
323
329
  const cdsType = this.getDataType(inputType, propertyContent.format);
324
330
 
package/lib/init/add.js CHANGED
@@ -3,6 +3,8 @@ const registered = new Map
3
3
  module.exports = {
4
4
  Plugin: require('./plugin'),
5
5
  merge: require('./merge').merge, // fluent API
6
+ removeFromYAML: require('./merge').removeFromYAML,
7
+ removeFromYAMLArray: require('./merge').removeFromYAMLArray,
6
8
  readProject: require('./projectReader').readProject,
7
9
  mvn: {
8
10
  add: require('./mvn').add
@@ -4,7 +4,7 @@ module.exports = {
4
4
  COMMAND_INIT: 'init',
5
5
  COMMAND_ADD: 'add',
6
6
 
7
- MAVEN_ARCHETYPE_VERSION: '2.5.0',
7
+ MAVEN_ARCHETYPE_VERSION: '2.7.0',
8
8
 
9
9
  OPTIONS: Object.freeze({
10
10
  JAVA: 'java',
@@ -50,7 +50,7 @@ module.exports = {
50
50
  REGEX_JAVA_PACKAGE: /^[a-z_][a-z0-9_]*(\.[a-z_][a-z0-9_]*)*$/g,
51
51
  // copied from mbt error message
52
52
  REGEX_JAVA_VERSION: /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$/,
53
- REGEX_PROJECT_NAME: /[^0-9a-zA-Z_-]/g,
53
+ REGEX_PROJECT_NAME: /[^0-9a-zA-Z_-]+/g,
54
54
 
55
55
  URLS: Object.freeze({
56
56
  PIPELINE_HELP: 'https://sap.github.io/jenkins-library/',