@sap/cds 6.6.1 → 6.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 (132) hide show
  1. package/CHANGELOG.md +67 -3
  2. package/README.md +1 -1
  3. package/apis/connect.d.ts +11 -4
  4. package/apis/core.d.ts +1 -1
  5. package/apis/csn.d.ts +1 -0
  6. package/apis/internal/inference.d.ts +15 -2
  7. package/apis/log.d.ts +10 -0
  8. package/apis/serve.d.ts +4 -9
  9. package/apis/services.d.ts +86 -19
  10. package/bin/build/buildTaskEngine.js +16 -42
  11. package/bin/build/constants.js +4 -2
  12. package/bin/build/provider/buildTaskProviderInternal.js +117 -85
  13. package/bin/build/provider/hana/index.js +6 -1
  14. package/bin/build/provider/mtx-extension/index.js +74 -34
  15. package/bin/build/provider/mtx-sidecar/index.js +3 -3
  16. package/bin/build/provider/nodejs/index.js +2 -2
  17. package/bin/build/util.js +63 -14
  18. package/bin/cds-serve.js +6 -0
  19. package/bin/cds.js +20 -4
  20. package/bin/deploy/to-hana/cfUtil.js +15 -1
  21. package/bin/deploy/to-hana/hana.js +1 -1
  22. package/bin/deploy/to-hana/hdiDeployUtil.js +1 -1
  23. package/bin/mtx/in-cds.js +2 -9
  24. package/bin/plugins.js +31 -0
  25. package/bin/serve.js +12 -12
  26. package/lib/compile/etc/_localized.js +1 -1
  27. package/lib/compile/for/lean_drafts.js +22 -6
  28. package/lib/compile/for/nodejs.js +4 -1
  29. package/lib/compile/load.js +4 -2
  30. package/lib/core/index.js +35 -15
  31. package/lib/dbs/cds-deploy.js +129 -133
  32. package/lib/env/cds-env.js +25 -17
  33. package/lib/env/cds-requires.js +10 -40
  34. package/lib/env/compat.js +12 -0
  35. package/lib/env/defaults.js +17 -9
  36. package/lib/env/plugins.js +29 -0
  37. package/lib/env/schemas/cds-rc.json +14 -0
  38. package/lib/index.js +3 -0
  39. package/lib/log/cds-log.js +7 -4
  40. package/lib/ql/CREATE.js +1 -1
  41. package/lib/ql/DELETE.js +1 -1
  42. package/lib/ql/DROP.js +3 -3
  43. package/lib/ql/INSERT.js +1 -1
  44. package/lib/ql/Query.js +14 -6
  45. package/lib/ql/SELECT.js +8 -2
  46. package/lib/ql/UPDATE.js +1 -1
  47. package/lib/ql/Whereable.js +1 -1
  48. package/lib/ql/cds-ql.js +1 -9
  49. package/lib/req/cds-context.js +1 -4
  50. package/lib/req/request.js +63 -2
  51. package/lib/req/response.js +3 -2
  52. package/lib/srv/bindings.js +69 -71
  53. package/lib/srv/cds-connect.js +4 -1
  54. package/lib/srv/cds-serve.js +4 -0
  55. package/lib/srv/middlewares/index.js +37 -6
  56. package/lib/srv/protocols/_legacy.js +1 -1
  57. package/lib/srv/protocols/index.js +1 -1
  58. package/lib/srv/srv-api.js +4 -6
  59. package/lib/srv/srv-dispatch.js +4 -3
  60. package/lib/srv/srv-handlers.js +1 -1
  61. package/lib/srv/srv-methods.js +8 -2
  62. package/lib/utils/cds-test.js +4 -1
  63. package/libx/_runtime/audit/Service.js +8 -9
  64. package/libx/_runtime/audit/generic/personal/index.js +1 -1
  65. package/libx/_runtime/audit/generic/personal/utils.js +1 -1
  66. package/libx/_runtime/audit/utils/v2.js +17 -20
  67. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +2 -0
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +11 -4
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +0 -1
  70. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +3 -3
  71. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -1
  72. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +4 -4
  73. package/libx/_runtime/cds-services/adapter/odata-v4/utils/oDataConfiguration.js +2 -2
  74. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -1
  75. package/libx/_runtime/cds-services/services/Service.js +1 -1
  76. package/libx/_runtime/cds-services/util/assert.js +41 -65
  77. package/libx/_runtime/common/code-ext/WorkerPool.js +90 -0
  78. package/libx/_runtime/common/code-ext/WorkerReq.js +0 -4
  79. package/libx/_runtime/common/code-ext/execute.js +28 -18
  80. package/libx/_runtime/common/code-ext/handlers.js +5 -4
  81. package/libx/_runtime/common/code-ext/worker.js +45 -3
  82. package/libx/_runtime/common/code-ext/workerQueryExecutor.js +8 -7
  83. package/libx/_runtime/common/composition/delete.js +1 -1
  84. package/libx/_runtime/common/composition/update.js +3 -5
  85. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  86. package/libx/_runtime/common/generic/auth/readOnly.js +5 -4
  87. package/libx/_runtime/common/generic/auth/restrict.js +7 -2
  88. package/libx/_runtime/common/generic/crud.js +12 -1
  89. package/libx/_runtime/common/generic/etag.js +11 -3
  90. package/libx/_runtime/common/generic/input.js +8 -6
  91. package/libx/_runtime/common/generic/paging.js +25 -8
  92. package/libx/_runtime/common/generic/put.js +1 -1
  93. package/libx/_runtime/common/generic/sorting.js +0 -1
  94. package/libx/_runtime/common/i18n/messages.properties +1 -0
  95. package/libx/_runtime/common/utils/cqn.js +5 -1
  96. package/libx/_runtime/common/utils/cqn2cqn4sql.js +2 -2
  97. package/libx/_runtime/common/utils/resolveView.js +14 -10
  98. package/libx/_runtime/common/utils/rewriteAsterisks.js +2 -3
  99. package/libx/_runtime/common/utils/templateProcessor.js +15 -17
  100. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +18 -6
  101. package/libx/_runtime/db/Service.js +1 -0
  102. package/libx/_runtime/db/data-conversion/post-processing.js +0 -18
  103. package/libx/_runtime/db/expand/expand-v2.js +2 -2
  104. package/libx/_runtime/db/expand/rawToExpanded.js +6 -6
  105. package/libx/_runtime/db/generic/integrity.js +1 -1
  106. package/libx/_runtime/db/utils/columns.js +5 -5
  107. package/libx/_runtime/fiori/generic/activate.js +3 -3
  108. package/libx/_runtime/fiori/generic/edit.js +1 -1
  109. package/libx/_runtime/fiori/generic/new.js +4 -0
  110. package/libx/_runtime/fiori/lean-draft.js +138 -46
  111. package/libx/_runtime/hana/execute.js +3 -1
  112. package/libx/_runtime/hana/pool.js +10 -2
  113. package/libx/_runtime/messaging/common-utils/AMQPClient.js +6 -1
  114. package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
  115. package/libx/_runtime/remote/Service.js +16 -13
  116. package/libx/_runtime/remote/utils/client.js +6 -1
  117. package/libx/_runtime/sqlite/Service.js +5 -59
  118. package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
  119. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +2 -2
  120. package/libx/_runtime/sqlite/execute.js +3 -1
  121. package/libx/_runtime/types/api.js +12 -3
  122. package/libx/odata/afterburner.js +36 -0
  123. package/libx/odata/cqn2odata.js +1 -1
  124. package/libx/odata/grammar.pegjs +5 -3
  125. package/libx/odata/parser.js +1 -1
  126. package/libx/odata/utils.js +1 -1
  127. package/libx/rest/RestAdapter.js +1 -1
  128. package/libx/rest/RestRequest.js +1 -0
  129. package/package.json +5 -2
  130. package/libx/_runtime/common/code-ext/workerQuery.js +0 -45
  131. package/libx/_runtime/common/constants/limit.js +0 -12
  132. package/libx/_runtime/common/utils/page.js +0 -39
@@ -1,11 +1,12 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
3
  const cds = require('../cds')
4
- const { hasJavaNature, getProperty, isStreamlinedMtx, getDefaultModelOptions, BuildError } = require('../util')
4
+ const { hasJavaNature, getProperty, isStreamlinedMtx, getDefaultModelOptions, BuildError, hasOptionValue } = require('../util')
5
5
  const BuildTaskProvider = require('../buildTaskProvider')
6
6
 
7
7
  const { FILE_EXT_CDS, BUILD_TASK_HANA, BUILD_TASK_FIORI, BUILD_TASK_JAVA, BUILD_TASK_JAVA_CF, BUILD_TASK_NODEJS, BUILD_TASK_NODE_CF, BUILD_TASK_MTX,
8
- BUILD_TASK_PREFIX, BUILD_TASKS, BUILD_TASK_MTX_SIDECAR, MTX_SIDECAR_FOLDER, BUILD_TASK_MTX_EXTENSION, CDS_MODEL_EXCLUDE_LIST } = require("../constants")
8
+ BUILD_TASK_PREFIX, BUILD_TASKS, BUILD_TASK_MTX_SIDECAR, MTX_SIDECAR_FOLDER, BUILD_TASK_MTX_EXTENSION, NODEJS_MODEL_EXCLUDE_LIST,
9
+ DEFAULT_BUILT_IN_MODELS } = require("../constants")
9
10
 
10
11
  class BuildTaskProviderInternal extends BuildTaskProvider {
11
12
  constructor(logger) {
@@ -37,41 +38,10 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
37
38
  task.for = task.for || taskFor
38
39
  task.use = task.use || `${BUILD_TASK_PREFIX}/${taskFor}`
39
40
 
40
- switch (taskFor) {
41
- case BUILD_TASK_HANA:
42
- task.src = task.src || BuildTaskProviderInternal._normalizePath(cds.env.folders.db)
43
- break
44
- case BUILD_TASK_JAVA:
45
- case BUILD_TASK_JAVA_CF:
46
- task.src = task.src || BuildTaskProviderInternal._normalizePath(cds.env.folders.srv)
47
- if (!task.options?.model) {
48
- BuildTaskProviderInternal._setDefaultModelOptionsForJava(task, projectPath)
49
- }
50
- break
51
- case BUILD_TASK_NODEJS:
52
- case BUILD_TASK_NODE_CF:
53
- task.src = task.src || BuildTaskProviderInternal._normalizePath(cds.env.folders.srv)
54
- break
55
- case BUILD_TASK_FIORI:
56
- task.src = task.src || BuildTaskProviderInternal._normalizePath(cds.env.folders.app)
57
- break
58
- case BUILD_TASK_MTX_SIDECAR:
59
- task.src = task.src || MTX_SIDECAR_FOLDER
60
- if (!task.options?.model && BuildTaskProviderInternal._hasJavaNature(projectPath, cds.env.folders.srv)) {
61
- BuildTaskProviderInternal._setDefaultModelOptionsForJava(task, projectPath)
62
- }
63
- break
64
- case BUILD_TASK_MTX_EXTENSION:
65
- case BUILD_TASK_MTX:
66
- if (isStreamlinedMtx()) {
67
- task.src = task.src || BuildTaskProviderInternal._normalizePath(cds.env.folders.srv)
68
- } else {
69
- task.src = task.src || "."
70
- }
71
- break
72
- default:
73
- throw new Error(`Unknown build task '${task.use || task.for}'`)
74
- }
41
+ BuildTaskProviderInternal._setDefaultSrcFolder(task)
42
+
43
+ // src folder needs to be initialized first
44
+ BuildTaskProviderInternal._setDefaultModel(task, projectPath)
75
45
  }
76
46
 
77
47
  async _createTasks(tasks, buildOptions, addRequiredTasks) {
@@ -136,10 +106,14 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
136
106
  if (srvTask) {
137
107
  tasks.push(srvTask)
138
108
  }
139
- // create mtx build task
140
- const mtxTask = this._createMtxTask(projectPath, srv, srv, tasks)
141
- if (mtxTask) {
142
- tasks.push(mtxTask)
109
+
110
+ // REVISIT: disable for cds-dk projectReader - causing error due to inconsistent state during project creation
111
+ if (buildOptions.resolve !== false) {
112
+ // create mtx build task
113
+ const mtxTask = this._createMtxTask(projectPath, srv, srv, tasks)
114
+ if (mtxTask) {
115
+ tasks.push(mtxTask)
116
+ }
143
117
  }
144
118
  }
145
119
  }
@@ -189,55 +163,60 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
189
163
  }
190
164
 
191
165
  _createMtxTask(projectPath, src, dest, tasks) {
192
- // MTX build task creation is NOT supported for Java projects
193
- if (tasks.some(task => task.for === BUILD_TASK_JAVA || task.for === BUILD_TASK_JAVA_CF)) {
194
- return null
195
- }
196
166
  this.logger.debug("determining mtx version of nodejs project")
197
167
 
198
168
  // preserve order of creation
199
169
  if (BuildTaskProviderInternal._isMtxExtension()) {
200
- this.logger.debug("Streamlined MTX extension app")
201
- return {
202
- for: BUILD_TASK_MTX_EXTENSION
203
- }
170
+ this.logger.debug("MTX extension app")
171
+ return { for: BUILD_TASK_MTX_EXTENSION }
204
172
  }
205
173
 
206
174
  if (isStreamlinedMtx()) {
207
- if (cds.env.requires["cds.xt.ModelProviderService"]?.kind === "rest") { // "cds.xt.ModelProviderService": "from-sidecar"
208
- this.logger.debug("Nodejs Streamlined MTX app with sidecar")
209
-
210
- const sidecarPath = path.join(projectPath, MTX_SIDECAR_FOLDER)
211
- if (!fs.existsSync(sidecarPath)) {
212
- throw new BuildError(`MTX sidecar directory '${sidecarPath}' not existing. Custom build task configuration necessary if the folder is named differently.`)
175
+ const sidecarPath = path.join(projectPath, MTX_SIDECAR_FOLDER)
176
+ let sidecarEnv
177
+ if (fs.existsSync(sidecarPath)) {
178
+ sidecarEnv = cds.env.for("cds", sidecarPath)
179
+ }
180
+ if (
181
+ cds.env.profiles.includes('with-mtx-sidecar') || // new presets
182
+ cds.env.requires["cds.xt.ModelProviderService"]?.external // for compatibility with former mtxs presets
183
+ ) {
184
+ this.logger.debug("MTX app with sidecar")
185
+ if (!sidecarEnv) {
186
+ throw new BuildError(`MTX sidecar directory '${sidecarPath}' not existing. Custom build task configuration required if the folder is named differently.`)
213
187
  }
214
- const sidecarEnv = cds.env.for("cds", sidecarPath)
215
- if (sidecarEnv.requires["cds.xt.ModelProviderService"]?.kind === "in-sidecar") {
216
- return {
217
- for: BUILD_TASK_MTX_SIDECAR
218
- }
188
+ if (!sidecarEnv.requires["cds.xt.ModelProviderService"]?._in_sidecar) {
189
+ throw new BuildError(`Invalid MTX sidecar configuration - profile 'mtx-sidecar' not set.`)
219
190
  }
220
- throw new BuildError("Invalid MTX sidecar configuration - \"cds.xt.ModelProviderService\": \"in-sidecar\" missing.")
191
+ return { for: BUILD_TASK_MTX_SIDECAR }
192
+ }
193
+ if (sidecarEnv?.requires["cds.xt.ModelProviderService"]?._in_sidecar) {
194
+ throw new BuildError(`MTX sidecar configuration requires profile 'with-mtx-sidecar' or custom build task configuration.`)
221
195
  }
222
196
 
223
- if (cds.env.requires["cds.xt.ModelProviderService"]?.kind === "in-sidecar") {
197
+ if (cds.env.requires["cds.xt.ModelProviderService"]?._in_sidecar) {
224
198
  // cds build is executed in sidecar folder
225
- throw new BuildError("Invalid working directory. Make sure to execute 'cds build' in CAP project root directory.")
199
+ throw new BuildError(`Invalid working directory ${projectPath}. Make sure to execute 'cds build' in the CAP project root directory.`)
226
200
  }
227
201
 
228
- this.logger.debug("Nodejs Streamlined MTX app without sidecar")
229
- return {
230
- for: BUILD_TASK_MTX,
231
- src
202
+ // without sidecar
203
+ if (!tasks.some(task => task.for === BUILD_TASK_JAVA || task.for === BUILD_TASK_JAVA_CF)) {
204
+ this.logger.debug("Nodejs MTX app without sidecar")
205
+ return {
206
+ for: BUILD_TASK_MTX,
207
+ src
208
+ }
232
209
  }
233
210
  }
234
211
 
235
- if (cds.env.requires.multitenancy) {
236
- this.logger.debug("Nodejs Classic MTX app without sidecar")
237
- return {
238
- src: ".",
239
- for: BUILD_TASK_MTX,
240
- dest
212
+ if (!tasks.some(task => task.for === BUILD_TASK_JAVA || task.for === BUILD_TASK_JAVA_CF)) {
213
+ if (cds.env.requires.multitenancy) {
214
+ this.logger.debug("Nodejs Classic MTX app without sidecar")
215
+ return {
216
+ src: ".",
217
+ for: BUILD_TASK_MTX,
218
+ dest
219
+ }
241
220
  }
242
221
  }
243
222
  }
@@ -284,25 +263,78 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
284
263
  return !!cds.env.extends
285
264
  }
286
265
 
287
- /**
288
- * REVISIT: filter model options in order to avoid that the bootstrap service gets added to the application model
289
- * https://github.tools.sap/cap/issues/issues/12770#issuecomment-1805719
290
- */
291
- static _setDefaultModelOptionsForJava(task, projectPath) {
292
- const defaultModelPaths = new Set(getDefaultModelOptions(projectPath))
293
- defaultModelPaths.add(task.src)
294
- CDS_MODEL_EXCLUDE_LIST.forEach(m => defaultModelPaths.delete(m))
266
+ static _setDefaultSrcFolder(task) {
267
+ switch (task.for) {
268
+ case BUILD_TASK_HANA:
269
+ task.src = task.src || this._normalizePath(cds.env.folders.db)
270
+ break
271
+ case BUILD_TASK_JAVA:
272
+ case BUILD_TASK_JAVA_CF:
273
+ case BUILD_TASK_NODEJS:
274
+ case BUILD_TASK_NODE_CF:
275
+ task.src = task.src || this._normalizePath(cds.env.folders.srv)
276
+ break
277
+ case BUILD_TASK_FIORI:
278
+ task.src = task.src || this._normalizePath(cds.env.folders.app)
279
+ break
280
+ case BUILD_TASK_MTX_SIDECAR:
281
+ task.src = task.src || MTX_SIDECAR_FOLDER
282
+ break
283
+ case BUILD_TASK_MTX_EXTENSION:
284
+ task.src = task.src || "."
285
+ break
286
+ case BUILD_TASK_MTX:
287
+ if (isStreamlinedMtx()) {
288
+ task.src = task.src || this._normalizePath(cds.env.folders.srv)
289
+ } else {
290
+ task.src = task.src || "."
291
+ }
292
+ break
293
+ default:
294
+ throw new Error(`Unknown build task '${task.use || task.for}'`)
295
+ }
296
+ }
297
+
298
+ static _setDefaultModel(task, projectPath) {
299
+ let taskModelPaths = task.options?.model
300
+ if (taskModelPaths && !Array.isArray(taskModelPaths)) {
301
+ taskModelPaths = [taskModelPaths]
302
+ }
295
303
  task.options = task.options || {}
296
- task.options.model = [...defaultModelPaths]
304
+ let defaultModelPaths = []
305
+ // add default models to custom build tasks if this hasn't been disabled
306
+ if (taskModelPaths?.length) {
307
+ if (!hasOptionValue(task.options?.[DEFAULT_BUILT_IN_MODELS], false)) {
308
+ // get the built-in models
309
+ defaultModelPaths = getDefaultModelOptions(projectPath).filter(p => p.match('@sap/cds'))
310
+ }
311
+ } else {
312
+ defaultModelPaths = getDefaultModelOptions(projectPath)
313
+ defaultModelPaths.push(task.src)
314
+ if (hasOptionValue(task.options?.[DEFAULT_BUILT_IN_MODELS], false)) {
315
+ // all default models except the built-in models
316
+ defaultModelPaths = defaultModelPaths.filter(p => !p.match('@sap/cds'))
317
+ }
318
+ }
319
+
320
+ // REVISIT: filter nodejs bootstrap service models for Java - issues/12770#issuecomment-1805719
321
+ if (this._hasJavaNature(projectPath, cds.env.folders.srv)) {
322
+ defaultModelPaths = defaultModelPaths.filter(p => !NODEJS_MODEL_EXCLUDE_LIST.includes(p))
323
+ }
324
+ // also add built-in models to custom build tasks already containing model options
325
+ if (!taskModelPaths || defaultModelPaths.length) {
326
+ task.options.model = [...new Set(taskModelPaths?.length ? taskModelPaths.concat(defaultModelPaths) : defaultModelPaths)]
327
+ }
297
328
  }
298
329
 
299
330
  /**
300
331
  * Returns whether this project is a java project or not.
301
332
  * @param {string} projectPath - the absolute project path
302
- * @param {string} src - the folder name of the service module
333
+ * @param {string} srv - the folder name of the service module
303
334
  */
304
- static _hasJavaNature(projectPath, src) {
305
- return hasJavaNature([path.join(projectPath, src), projectPath])
335
+ static _hasJavaNature(projectPath, srv) {
336
+ srv = this._getModuleFolder(projectPath, Array.isArray(srv) ? srv : [srv])
337
+ return hasJavaNature([projectPath, srv && path.join(projectPath, srv)])
306
338
  }
307
339
 
308
340
  static _getForValueFromTask(task) {
@@ -436,7 +436,12 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
436
436
  if (fs.existsSync(file)) {
437
437
  const undeployList = JSON.parse((await fs.promises.readFile(file)).toString(), 'utf-8')
438
438
  if (Array.isArray(undeployList)) {
439
- undeployList.forEach(entry => result.add(path.extname(entry)))
439
+ undeployList.forEach(entry => {
440
+ const extName = path.extname(entry)
441
+ if (extName) {
442
+ result.add(extName)
443
+ }
444
+ })
440
445
  }
441
446
  }
442
447
  return result
@@ -3,9 +3,9 @@ const fs = require('fs')
3
3
  const cds = require('../../cds')
4
4
 
5
5
  const BuildTaskHandlerInternal = require('../buildTaskHandlerInternal')
6
- const { FOLDER_GEN } = require('../../constants')
6
+ const { FOLDER_GEN, EXTENSION_POINT_VALIDATION } = require('../../constants')
7
7
  const ResourcesTarBuilder = require('../mtx/resourcesTarBuilder')
8
- const { BuildError } = require('../../util')
8
+ const { BuildError, BuildMessage } = require('../../util')
9
9
 
10
10
  class MtxExtensionModuleBuilder extends BuildTaskHandlerInternal {
11
11
  init() {
@@ -18,43 +18,32 @@ class MtxExtensionModuleBuilder extends BuildTaskHandlerInternal {
18
18
  async build() {
19
19
  const { src, dest } = this.task
20
20
  const destExt = path.join(dest, 'ext')
21
-
22
- const packageJson = path.join(src, 'package.json')
23
- if (!fs.existsSync(packageJson)) {
24
- throw new BuildError(`The package.json file is missing`)
25
- }
26
- await this.copy(packageJson).to(path.join(destExt, 'package.json'))
27
-
28
- // validate existence of base model by simply checking existence of appPackage folder
29
- // cds.resolve might fail for the extension migration use case as no index.csn file exists.
30
- // A compilation error is thrown anyhow if any base model using statement cannot be resolved.
31
- const appPackage = MtxExtensionModuleBuilder._getAppPackageName()
32
- if (!fs.existsSync(path.join(src, 'node_modules', appPackage))) {
33
- throw new BuildError(`The SaaS application base model '${appPackage}' is missing. Have you run the 'cds pull' command?`)
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
23
+ // 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?`)
34
27
  }
35
28
 
36
- // copy handlers
37
- const folders = [path.join(src, cds.env.folders.srv, 'handlers')]
38
- await this.copyNativeContent(src, destExt, res => {
39
- if (fs.statSync(res).isDirectory()) {
40
- return folders.some(folder => folder.startsWith(res))
41
- }
42
- if (folders.includes(path.dirname(res)) && /\.js$/.test(res)) {
43
- return true
44
- }
45
- })
46
-
29
+ // full compile of the extension using base model - ensuring consistency
47
30
  const model = await this.model()
31
+
48
32
  if (model) {
49
33
  // extension CSN using parsed format
50
34
  const options = { ...this.options(), flavor: 'parsed' }
51
- const extCsn = await cds.load(this.resolveModel(), options)
52
- if (extCsn.requires) {
53
- extCsn.requires.length = 0
35
+ const extModel = await cds.load(this.resolveModel(), options)
36
+ if (extModel.requires) {
37
+ extModel.requires.length = 0
54
38
  }
55
- await this.compileToJson(extCsn, path.join(destExt, 'extension.csn'))
56
39
 
57
- await this.collectLanguageBundles(extCsn, path.join(destExt, 'i18n'))
40
+ if (!this.hasBuildOption(EXTENSION_POINT_VALIDATION, false)) {
41
+ this._lintExtModel(extModel, model) // throws error in case of linting errors
42
+ }
43
+
44
+ await this.compileToJson(extModel, path.join(destExt, 'extension.csn'))
45
+
46
+ await this.collectLanguageBundles(extModel, path.join(destExt, 'i18n'))
58
47
 
59
48
  const files = Object.keys(await cds.deploy.resources(model))
60
49
  if (files.length > 0) {
@@ -68,12 +57,63 @@ class MtxExtensionModuleBuilder extends BuildTaskHandlerInternal {
68
57
  )
69
58
  }
70
59
  }
71
- // add all resources contained in the 'ext' folder
60
+
61
+ const packageJson = path.join(src, 'package.json')
62
+ if (!fs.existsSync(packageJson)) {
63
+ throw new BuildError(`package.json file not found in ${src}`)
64
+ }
65
+ await this.copy(packageJson).to(path.join(destExt, 'package.json'))
66
+
67
+ // copy handlers
68
+ const folders = [path.join(src, cds.env.folders.srv, 'handlers')]
69
+ await this.copyNativeContent(src, destExt, res => {
70
+ if (fs.statSync(res).isDirectory()) {
71
+ return folders.some(folder => folder.startsWith(res))
72
+ }
73
+ if (folders.includes(path.dirname(res)) && /\.js$/.test(res)) {
74
+ return true
75
+ }
76
+ })
77
+
78
+ // add all resources contained in 'gen/ext' folder
72
79
  await new ResourcesTarBuilder(this).writeTarFile(path.join(this.task.dest, 'extension.tgz'), destExt)
73
80
  }
74
81
 
75
- static _getAppPackageName() {
76
- return cds.env.extends || '_base';
82
+ _lintExtModel(extModel, model) {
83
+ const linter = this._linter()
84
+ if (!linter) {
85
+ return
86
+ }
87
+ const env = cds.env.for('cds', path.join(this.task.src, this._getAppPackageFolder()))
88
+ this.logger._debug && this.logger.debug(`Saas extension point restrictions:\n${env.requires?.['cds.xt.ExtensibilityService']}`)
89
+
90
+ const messages = linter.lint(extModel, model, env)
91
+ if (messages.length) {
92
+ // REVISIT: lint messages can be passed as is with cds-mtxs version >= 1.7
93
+ throw new BuildError('SaaS extension point restrictions violated. Check the concrete restrictions defined by the SaaS app provider.',
94
+ messages.map(f => new BuildMessage(f.message, f.severity, f.location || f.element?.$location)))
95
+ }
96
+ }
97
+
98
+ _linter() {
99
+ let linter
100
+ try {
101
+ // Make sure cds-mtxs APIs are loaded
102
+ linter = require('@sap/cds-mtxs').xt?.linter // eslint-disable-line cds/no-missing-dependencies
103
+ if (!linter) {
104
+ this.pushMessage('MTXS linter cannot be loaded. Update of @sap/cds-dk and @sap/cds-mtxs modules required? Skipping extension model lint step.')
105
+ return null // too old mtxs
106
+ }
107
+ } catch (e) {
108
+ if (e.code !== 'MODULE_NOT_FOUND') throw e
109
+ this.pushMessage('MTXS linter cannot be loaded, @sap/cds-mtxs not installed. Skipping extension model lint step.')
110
+ }
111
+ return linter
112
+ }
113
+
114
+ _getAppPackageFolder() {
115
+ return path.join('node_modules', cds.env.extends || '_base');
77
116
  }
78
117
  }
118
+
79
119
  module.exports = MtxExtensionModuleBuilder
@@ -58,10 +58,10 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
58
58
  * @param {object} sidecarEnv cds env based on the sidecar dir
59
59
  */
60
60
  async _buildMainApp(sidecarEnv) {
61
- if (sidecarEnv.requires['cds.xt.ModelProviderService']?.kind !== 'in-sidecar') {
62
- throw new BuildError('Invalid MTX sidecar configuration - "cds.xt.ModelProviderService": "in-sidecar" missing.')
63
- }
64
61
  let main = sidecarEnv.requires['cds.xt.ModelProviderService']?.root
62
+ if (!main) {
63
+ throw new BuildError('Invalid MTX sidecar configuration - "cds/requires/cds.xt.ModelProviderService/root" missing.')
64
+ }
65
65
  const profiles = cds.env.profiles || []
66
66
 
67
67
  if (!profiles.includes("production") && !profiles.includes("prod")) {
@@ -3,7 +3,7 @@ const path = require('path')
3
3
  const cds = require('../../cds')
4
4
  const BuildTaskHandlerEdmx = require('../buildTaskHandlerEdmx')
5
5
  const { BuildError } = require('../../util')
6
- const { BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY, ODATA_VERSION_V2, FOLDER_GEN, BUILD_NODEJS_EDMX_GENERAION, EDMX_GENERATION,
6
+ const { BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY, ODATA_VERSION_V2, FOLDER_GEN, BUILD_NODEJS_EDMX_GENERATION, EDMX_GENERATION,
7
7
  SKIP_PACKAGE_JSON_GENERATION, CONTENT_EDMX, CONTENT_PACKAGE_JSON, CONTENT_PACKAGELOCK_JSON, CONTENT_NPMRC, CONTENT_CDSRC_JSON,
8
8
  CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON } = require('../../constants')
9
9
  const { WARNING } = BuildTaskHandlerEdmx
@@ -19,7 +19,7 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
19
19
  this.task.options[CONTENT_CDSRC_JSON] = !this.hasBuildOption(CONTENT_CDSRC_JSON, false) ? true : false
20
20
 
21
21
  // default value false
22
- this.task.options[CONTENT_EDMX] = this.hasBuildOption(CONTENT_EDMX, true) || this.hasCdsEnvOption(BUILD_NODEJS_EDMX_GENERAION, true) || this.hasBuildOption(EDMX_GENERATION, true) ? true : false
22
+ this.task.options[CONTENT_EDMX] = this.hasBuildOption(CONTENT_EDMX, true) || this.hasCdsEnvOption(BUILD_NODEJS_EDMX_GENERATION, true) || this.hasBuildOption(EDMX_GENERATION, true) ? true : false
23
23
  this.task.options[CONTENT_ENV] = this.hasBuildOption(CONTENT_ENV, true) ? true : false
24
24
  this.task.options[CONTENT_DEFAULT_ENV_JSON] = this.hasBuildOption(CONTENT_DEFAULT_ENV_JSON, true) ? true : false
25
25
 
package/bin/build/util.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
3
  const cds = require('./cds')
4
- const { SEVERITY_ERROR, CDS_MODEL_EXCLUDE_LIST, FILE_EXT_CDS } = require('./constants')
4
+ const { SEVERITY_ERROR, FILE_EXT_CDS } = require('./constants')
5
5
 
6
6
  function getProperty(src, segments) {
7
7
  segments = Array.isArray(segments) ? segments : segments.split('.')
@@ -126,20 +126,21 @@ function isStreamlinedMtx() {
126
126
  return false
127
127
  }
128
128
  return (cds.env.requires.toggles
129
+ || cds.env.profiles.includes('with-mtx-sidecar') // new presets
129
130
  || cds.env.requires['cds.xt.ModelProviderService']
130
131
  || (typeof cds.env.requires.multitenancy === "object")
131
132
  ) && !(() => { try { return cds.mtx } catch (e) { /**/ } })()
132
133
  }
133
134
 
134
135
  /**
135
- * Returns a list of fully qualified model names belonging to the '@sap' namespace that cannot be resolved.
136
- * E.g. the npm module might NOT be installed.
137
- * @param {Array} modelPaths
136
+ * Returns a list of fully qualified model names belonging to the '@sap' namespace that cannot be resolved.
137
+ * E.g. the module might NOT have been installed.
138
+ * @param {Array} modelPaths
138
139
  * @returns {Array}
139
140
  */
140
141
  function resolveRequiredSapModels(modelPaths) {
141
- return modelPaths.filter(p => {
142
- if (p.startsWith('@sap/') && !CDS_MODEL_EXCLUDE_LIST.includes(p)) {
142
+ return Array.isArray(modelPaths) && modelPaths.filter(p => {
143
+ if (p.startsWith('@sap/')) {
143
144
  const files = cds.resolve(p)
144
145
  return !files || files.length === 0
145
146
  }
@@ -173,7 +174,7 @@ function _pushModelPaths(projectPath, ...modelPaths) {
173
174
  }
174
175
  }
175
176
  })
176
- return model
177
+ return [...model]
177
178
  }
178
179
 
179
180
  function flatten(modelPaths) {
@@ -203,23 +204,71 @@ async function copy(src, dest) {
203
204
  }
204
205
  }
205
206
 
206
- class BuildMessage extends Error {
207
- constructor(message, severity = SEVERITY_ERROR) {
208
- super(message)
207
+ /**
208
+ * Return gnu-style error string for location `loc`:
209
+ * - 'File:Line:Col' without `loc.end`
210
+ * - 'File:Line:StartCol-EndCol' if Line = start.line = end.line
211
+ * - 'File:StartLine.StartCol-EndLine.EndCol' otherwise
212
+ *
213
+ * @param {CSN.Location|CSN.Location} location
214
+ */
215
+ function _locationString(loc) {
216
+ if (!loc)
217
+ return '<???>';
218
+ if (!(loc instanceof Object))
219
+ return loc;
220
+ if (!loc.line) {
221
+ return loc.file;
222
+ }
223
+ else if (!loc.endLine) {
224
+ return (loc.col)
225
+ ? `${loc.file}:${loc.line}:${loc.col}`
226
+ : `${loc.file}:${loc.line}`;
227
+ }
228
+
229
+ return (loc.line === loc.endLine)
230
+ ? `${loc.file}:${loc.line}:${loc.col}-${loc.endCol}`
231
+ : `${loc.file}:${loc.line}.${loc.col}-${loc.endLine}.${loc.endCol}`;
232
+ }
233
+
234
+ /**
235
+ * Class for individual build message.
236
+ *
237
+ * @class BuildMessage
238
+ */
239
+ class BuildMessage {
240
+ /**
241
+ * Creates an instance of BuildMessage.
242
+ * @param {string} message The message text
243
+ * @param {string} [severity='Error'] Severity: Debug, Info, Warning, Error
244
+ * @param {any} location Location of the message
245
+ *
246
+ * @memberOf BuildMessage
247
+ */
248
+ constructor(message, severity = SEVERITY_ERROR, location) {
249
+ this.message = message
209
250
  this.name = "BuildMessage"
210
251
  this.severity = severity
211
- this.stack = ""
252
+ this.$location = location
212
253
  }
213
254
  toString() {
214
- return this.severity + ": " + this.message
255
+ return `${this.$location?.file ? _locationString(this.$location) + ':' : ''} ${this.severity}: ${this.message}`
215
256
  }
216
257
  }
217
258
 
218
- class BuildError extends BuildMessage {
259
+ /**
260
+ * Class for combined build and compiler errors.
261
+ * Additional members:
262
+ * messages: vector of detailed build messages
263
+ * @class BuildError
264
+ * @extends {Error}
265
+ */
266
+ class BuildError extends Error {
219
267
  constructor(message, messages = []) {
220
- super(message, SEVERITY_ERROR)
268
+ super(message)
221
269
  this.name = "BuildError"
222
270
  this.messages = Array.isArray(messages) ? messages : [messages]
271
+ this.stack = ""
223
272
  }
224
273
 
225
274
  // for compatibility reasons
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+
3
+ const cds = require('./cds')
4
+ const args = process.argv.slice(2)
5
+
6
+ cds.exec ('serve', ...args)
package/bin/cds.js CHANGED
@@ -10,13 +10,20 @@ const cli = { //NOSONAR
10
10
  exec (cmd = process.argv[2], ...argv) {
11
11
  if (!argv.length) argv = process.argv.slice(3)
12
12
  if (cmd in this.Shortcuts) cmd = process.argv[2] = this.Shortcuts[cmd]
13
- let task = this.load ('./'+cmd)
14
- if (task && cmd !== 'build') return task.apply (this, this.args(task,argv))
13
+ if (!module.parent && ['serve', 'run'].includes(cmd)) _deprecationHint(cmd)
14
+
15
+ const task = this.load ('./'+cmd)
16
+
17
+ let args
18
+ try { args = task && cmd !== 'build' && this.args(task, argv) }
19
+ catch (err) { process.exitCode = 1; return console.error(err) }
20
+
21
+ if (task && cmd !== 'build') return task.apply (this, args)
15
22
 
16
23
  // delegate to cds-dk for rest of commands (usually shortcuts like `cds c` including cds build if cds-dk is available)
17
24
  const dk = _requireDk('cds')
18
25
  if (dk) return dk.exec(cmd, ...argv)
19
- if (cmd === 'build') return task.apply (this, this.args(task,argv))
26
+ if (cmd === 'build') return task.apply (this, args)
20
27
  _die_needsDk (cmd)
21
28
  },
22
29
 
@@ -40,7 +47,7 @@ const cli = { //NOSONAR
40
47
  else if ((k = o.indexOf(a)) >= 0) add(o[k],argv[++i])
41
48
  else if ((k = f.indexOf(a)) >= 0) add(f[k])
42
49
  else if (_global.test(a)) add_global(a, _flags[a] || argv[++i])
43
- else throw cds.error ('invalid option: '+ a)
50
+ else throw 'Invalid option: '+ a
44
51
  }
45
52
 
46
53
  function add (k,v) { options[k.slice(2)] = v || true }
@@ -94,6 +101,15 @@ const _die_needsDk = (cmd) => {
94
101
  return console.error (message)
95
102
  }
96
103
 
104
+ function _deprecationHint(cmd) {
105
+ console.error(require('./utils/term').warn(`
106
+ Warning: \`cds ${cmd}\` will be removed with the next major version.
107
+ Use \`cds-serve\` instead in your start script. Set it with:
108
+
109
+ npm pkg set scripts.start="cds-serve"
110
+ `))
111
+ }
112
+
97
113
  require('util').inspect.defaultOptions = {
98
114
  colors: !!process.stdout.isTTY && !!process.stderr.isTTY,
99
115
  depth:11, breakLength:111