@sap/cds-dk 8.4.2 → 8.5.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 (51) hide show
  1. package/CHANGELOG.md +41 -3
  2. package/bin/cds.js +49 -28
  3. package/bin/debug.js +90 -0
  4. package/bin/repl.js +50 -4
  5. package/bin/watch.js +13 -3
  6. package/lib/bind/userCdsrcJsonStorageProvider.js +0 -19
  7. package/lib/build/buildTaskEngine.js +4 -4
  8. package/lib/build/provider/hana/2migration.js +34 -20
  9. package/lib/build/provider/hana/index.js +74 -41
  10. package/lib/build/provider/internalBuildTaskProvider.js +1 -1
  11. package/lib/build/provider/mtx-extension/index.js +4 -4
  12. package/lib/build/provider/mtx-sidecar/index.js +2 -2
  13. package/lib/build/provider/nodejs/index.js +3 -19
  14. package/lib/build/util.js +41 -17
  15. package/lib/cds.js +10 -2
  16. package/lib/deploy/to-hana/hdiDeployUtil.js +14 -10
  17. package/lib/import/message.js +2 -1
  18. package/lib/import/odata/metadataconverter/MetadataConverter.js +5 -0
  19. package/lib/import/odata/v4parser.js +32 -43
  20. package/lib/init/add.js +26 -0
  21. package/lib/init/constants.js +5 -6
  22. package/lib/init/index.js +17 -25
  23. package/lib/init/merge.js +2 -5
  24. package/lib/init/projectReader.js +76 -9
  25. package/lib/init/registries/mta.js +0 -1
  26. package/lib/init/template/approuter/files/package.json +1 -1
  27. package/lib/init/template/cloud-logging/files/package.json.hbs +1 -12
  28. package/lib/init/template/cloud-logging/files/values.yaml.hbs +16 -0
  29. package/lib/init/template/cloud-logging/index.js +14 -10
  30. package/lib/init/template/data/as-json.js +1 -1
  31. package/lib/init/template/data/index.js +1 -26
  32. package/lib/init/template/esm/files/package.json.hbs +3 -0
  33. package/lib/init/template/esm/index.js +16 -0
  34. package/lib/init/template/handler/files/handler.xs.hbs +56 -0
  35. package/lib/init/template/handler/index.js +84 -9
  36. package/lib/init/template/helm/index.js +4 -1
  37. package/lib/init/template/html5-repo/files/xs-app.json.hbs +1 -0
  38. package/lib/init/template/html5-repo/index.js +2 -0
  39. package/lib/init/template/http/index.js +102 -35
  40. package/lib/init/template/nodejs/files/package.json.hbs +1 -1
  41. package/lib/init/template/sample/files/nodejs/{admin-service.js → admin-service.js.hbs} +8 -1
  42. package/lib/init/template/sample/files/nodejs/{cat-service.js → cat-service.js.hbs} +8 -3
  43. package/lib/init/template/sample/index.js +4 -2
  44. package/lib/init/template/telemetry/files/package.json.hbs +18 -0
  45. package/lib/init/template/telemetry/index.js +8 -2
  46. package/lib/init/template/typescript/files/package.json +2 -2
  47. package/lib/init/template/workzone-standard/index.js +1 -1
  48. package/lib/init/types.d.ts +0 -6
  49. package/lib/watch/node-watch.js +25 -9
  50. package/npm-shrinkwrap.json +63 -48
  51. package/package.json +1 -1
@@ -1,9 +1,9 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
3
  const cds = require('../../../cds')
4
- const to_hdbmigration = require('./2migration')
4
+ const { compileToHdbMigration, getJournalEntities, getMigrationTableName } = require('./2migration')
5
5
  const InternalBuildPlugin = require('../internalBuildPlugin')
6
- const { BuildError, relativePaths, BuildMessage, getWorkspaces, hasOptionValue, getFiles } = require('../../util')
6
+ const { BuildError, relativePaths, BuildMessage, hasOptionValue, getFiles, getWorkspacePaths } = require('../../util')
7
7
  const { OUTPUT_MODE_NONE, OUTPUT_MODE, CONTENT_PACKAGE_JSON, CONTENT_HDBTABLEDATA, CSV_FILE_DETECTION,
8
8
  CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, CONTENT_NODE_MODULES, OUTPUT_MODE_RESULT, CONTINUE_UNRESOLVED_SCHEMA_CHANGES,
9
9
  CSV_FILE_TARGET, OPTION_WS } = require('../../constants')
@@ -38,14 +38,15 @@ class HanaBuildPlugin extends InternalBuildPlugin {
38
38
  if (!model) {
39
39
  return
40
40
  }
41
- let workspaceFiles
42
- if (cds.cli.options?.[OPTION_WS] || hasOptionValue(this.task.options[OPTION_WS], true)) {
43
- workspaceFiles = await this._getWorkspaceFiles()
41
+ let wsPaths = []
42
+ if(getJournalEntities(model).length || cds.cli.options?.[OPTION_WS] || hasOptionValue(this.task.options[OPTION_WS], true)) {
43
+ // workspace paths don't include the current project root
44
+ wsPaths = await getWorkspacePaths()
44
45
  }
45
46
 
46
47
  // the order of 1 and 2 is important
47
48
  // 1. compile
48
- const hdiPlugins = await this._compileToHana(model, workspaceFiles)
49
+ const hdiPlugins = await this._compileToHana(model, wsPaths)
49
50
 
50
51
  if (!this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_NONE)) {
51
52
  // 2. copy resources
@@ -54,16 +55,6 @@ class HanaBuildPlugin extends InternalBuildPlugin {
54
55
  } else {
55
56
  await this._copyResourcesExt(src, dest, model)
56
57
  }
57
- // copy native hana artifacts from workspaces
58
- if (workspaceFiles) {
59
- const dest = path.join(this.task.dest, this.isStagingBuild() ? 'src' : 'src/gen')
60
- await Promise.all(
61
- workspaceFiles.map((src) => {
62
- const { dir, filePath } = src
63
- return this.copy(path.join(dir, filePath)).to(path.join(dest, filePath))
64
- })
65
- )
66
- }
67
58
 
68
59
  // create additional stuff in dest
69
60
  await this._writeHdiConfig(hdiPlugins)
@@ -78,6 +69,28 @@ class HanaBuildPlugin extends InternalBuildPlugin {
78
69
  if (!this.hasBuildOption(CONTENT_PACKAGE_JSON, false)) {
79
70
  await this._writePackageJson()
80
71
  }
72
+
73
+ // copy native hana artifacts from other workspaces including .hdbmigrationtable files defined for
74
+ // common model artifacts that are not part of the current project
75
+ if (wsPaths.length || cds.cli.options?.[OPTION_WS] || hasOptionValue(this.task.options[OPTION_WS], true)) {
76
+ // first is winning, e.g. a .hdiconfig file might exist in multiple workspaces
77
+ const wsFiles = (await this._getWorkspaceFiles(wsPaths)).reduce((acc, src) => {
78
+ const { filePath } = src;
79
+ if (!acc.some(file => file.filePath === filePath)) {
80
+ acc.push(src);
81
+ }
82
+ return acc;
83
+ }, []);
84
+
85
+ // copy artifacts to the src/gen folder in inplace mode
86
+ const dest = path.join(this.task.dest, this.isStagingBuild() ? 'src' : 'src/gen')
87
+ await Promise.all(
88
+ wsFiles.map((src) => {
89
+ const { dir, filePath } = src
90
+ return this.copy(path.join(dir, filePath)).to(path.join(dest, filePath))
91
+ })
92
+ )
93
+ }
81
94
  }
82
95
  return this._result
83
96
  }
@@ -264,20 +277,18 @@ class HanaBuildPlugin extends InternalBuildPlugin {
264
277
  *
265
278
  * @param {object} model The compiled csn model
266
279
  */
267
- async _compileToHana(model, workspaceFiles) {
280
+ async _compileToHana(model, wsPaths) {
268
281
  // compile to old format (.hdbcds) or new format (.hdbtable / .hdbview)
269
282
  const format = this.getBuildOption(DEPLOY_FORMAT) || cds.env.requires.db?.[DEPLOY_FORMAT] || cds.env.hana?.[DEPLOY_FORMAT]
270
283
  if (!cds.compile.to[format]) {
271
284
  return Promise.reject(new Error(`Invalid deploy-format defined: ${format}`))
272
285
  }
273
286
 
274
- if (cds.env.features.journal === false
275
- || format === 'hdbcds'
276
- || (!cds.compile.to.hana && Object.values(model.definitions).every(def => !def['@cds.persistence.journal']))) {
287
+ if (cds.env.features.journal === false || format === 'hdbcds') {
277
288
  // compatibility with existing cds.compile.to.hdbtable plugins cds < 8.0.0
278
289
  return await this._compileToHdb(model, format)
279
290
  } else {
280
- return await this._compileToHdbmigration(model, format, workspaceFiles)
291
+ return await this._compileToHdbmigration(model, format, wsPaths)
281
292
  }
282
293
  }
283
294
 
@@ -313,7 +324,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
313
324
  return hdiPlugins
314
325
  }
315
326
 
316
- async _compileToHdbmigration(model, format, workspaceFiles) {
327
+ async _compileToHdbmigration(model, format, wsPaths) {
317
328
  const hdiPlugins = new Set()
318
329
  const relDestDir = path.relative(this.task.dest, this.task.options.compileDest)
319
330
  const relDbDestDir = path.relative(cds.root, this.task.options.compileDest)
@@ -328,19 +339,30 @@ class HanaBuildPlugin extends InternalBuildPlugin {
328
339
  if (fs.existsSync(lastDevCsnDir)) {
329
340
  lastDev = JSON.parse((await fs.promises.readFile(lastDevCsnDir, 'utf-8')).toString())
330
341
  }
342
+
331
343
  // enforces sqlNames option for compiler in tests, pass options from cds env, ensures that the correct format is taken
332
344
  const options = { ...this.options(), messages: this.messages, sql_mapping: cds.env.sql.names, "deploy-format": format }
333
-
334
- const { definitions, afterImage } = await to_hdbmigration(model, lastDev, dbSrcDir, options)
345
+ const { definitions, afterImage } = await compileToHdbMigration(model, lastDev, dbSrcDir, options)
335
346
  let validationError
336
347
 
348
+ // determine the migration tables originating from a different workspaces
349
+ const journalEntities = getJournalEntities(model)
350
+ const wsMigrationTables = new Set()
351
+ if (journalEntities.length) {
352
+ for (const entity of journalEntities) {
353
+ if (entity['$location']?.file && wsPaths.some(wsPath => path.resolve(cds.root, entity['$location'].file).startsWith(wsPath))) {
354
+ wsMigrationTables.add(getMigrationTableName(entity, model))
355
+ }
356
+ }
357
+ }
358
+
337
359
  for (const { name, suffix, content, changed } of definitions) {
338
360
  hdiPlugins.add(suffix)
339
361
  const fileName = name + suffix
340
362
  if (suffix === FILE_EXT_HDBMIGRATIONTABLE) {
341
- // Skip the .hdbmigrationtable file if its origin is a different workspace.
342
- // The workspace build copies artifacts located in 'db/src/**' workspace folders.
343
- if (!workspaceFiles?.find(wsFile => wsFile.fileName === fileName)) {
363
+ // do not create .hdbmigrationtable files originating from a different workspace
364
+ // the --ws build option will copy these artifacts to either gen/db/src or db/src/gen
365
+ if (!wsMigrationTables.has(name)) {
344
366
  migrationTableFiles.push(path.join(relDbSrcDir, fileName))
345
367
  if (changed) {
346
368
  if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
@@ -350,6 +372,8 @@ class HanaBuildPlugin extends InternalBuildPlugin {
350
372
  } else {
351
373
  DEBUG?.(`no change, keep existing ${fileName}`)
352
374
  }
375
+ } else {
376
+ DEBUG?.(`skip generation of migration table [${fileName}] as it originates from a different workspace`)
353
377
  }
354
378
  } else {
355
379
  if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
@@ -547,21 +571,26 @@ class HanaBuildPlugin extends InternalBuildPlugin {
547
571
 
548
572
  /**
549
573
  * Returns all hana native artifacts located in db/src/** workspace folders.
574
+ * @param {Array} workspacePaths Array of workspace paths, 'cds.root' is excluded from the list
550
575
  * @returns {Array} Array of workspace files, each entry contains the db src directory,
551
576
  * the relative file path and the file name.
552
577
  */
553
- async _getWorkspaceFiles() {
578
+ async _getWorkspaceFiles(workspacePaths) {
579
+ if (!workspacePaths || !workspacePaths.length) {
580
+ return []
581
+ }
554
582
  const dbPaths = new Set()
555
- const workspaces = await getWorkspaces()
556
-
557
- workspaces.forEach(workspace => {
558
- const dbSrc = path.join(cds.root, workspace, 'db/src')
559
- const dbCfg = path.join(cds.root, workspace, 'db/cfg')
560
- if (fs.existsSync(dbSrc)) {
561
- dbPaths.add(dbSrc)
562
- }
563
- if (fs.existsSync(dbCfg)) {
564
- dbPaths.add(dbCfg)
583
+ workspacePaths.forEach(workspacePath => {
584
+ // skip workspace matching the current project if cds build is executed in a workspace folder
585
+ if (cds.root !== workspacePath) {
586
+ const dbSrc = path.join(workspacePath, 'db/src')
587
+ const dbCfg = path.join(workspacePath, 'db/cfg')
588
+ if (fs.existsSync(dbSrc)) {
589
+ dbPaths.add(dbSrc)
590
+ }
591
+ if (fs.existsSync(dbCfg)) {
592
+ dbPaths.add(dbCfg)
593
+ }
565
594
  }
566
595
  })
567
596
  const workspaceFiles = [...dbPaths].reduce((acc, src) => {
@@ -572,11 +601,15 @@ class HanaBuildPlugin extends InternalBuildPlugin {
572
601
  }
573
602
  return true
574
603
  })
575
- files.forEach(file => acc.add({ dir: src, filePath: path.relative(src, file), fileName: path.basename(file) }))
604
+ files.forEach(file => acc.push({ dir: src, filePath: path.relative(src, file), fileName: path.basename(file) }))
576
605
  return acc
577
- }, new Set())
606
+ }, [])
607
+
608
+ return workspaceFiles
609
+ }
578
610
 
579
- return [...workspaceFiles]
611
+ _hasJournalDefinitions(model) {
612
+ return Object.values(model.definitions).some(def => def['@cds.persistence.journal'])
580
613
  }
581
614
 
582
615
  static async _readTemplateAsJson(template) {
@@ -167,7 +167,7 @@ class InternalBuildTaskProvider extends BuildTaskProvider {
167
167
  if (!sidecarEnv.requires["cds.xt.ModelProviderService"]?._in_sidecar) {
168
168
  if (sidecarEnv.profiles?.includes('mtx-sidecar')) {
169
169
  // profile has been set, but cannot be resolved
170
- 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.")
170
+ throw new BuildError("MTX configuration cannot be resolved. Run the 'npm install' command to install up-to-date versions of @sap/cds-mtxs and @sap/cds.")
171
171
  }
172
172
  throw new BuildError(`Invalid MTX sidecar configuration - profile 'mtx-sidecar' not set.`)
173
173
  }
@@ -85,13 +85,13 @@ class MtxExtensionBuildPlugin extends InternalBuildPlugin {
85
85
  // existence already checked
86
86
  await this.copy(path.join(cds.root, 'package.json')).to(path.join(destExt, 'package.json'))
87
87
 
88
- // copy handlers
89
- const folders = [path.join(cds.root, cds.env.folders.srv, 'handlers')]
88
+ // copy handlers from srv folder
89
+ const folder = path.join(cds.root, cds.env.folders.srv).replace(/[/\\]$/, '')
90
90
  await this.copyNativeContent(cds.root, destExt, res => {
91
91
  if (fs.statSync(res).isDirectory()) {
92
- return folders.some(folder => folder.startsWith(res))
92
+ return res.startsWith(folder)
93
93
  }
94
- if (folders.includes(path.dirname(res)) && /\.js$/.test(res)) {
94
+ if (path.dirname(res).startsWith(folder) && /\.js$/.test(res)) {
95
95
  return true
96
96
  }
97
97
  })
@@ -118,7 +118,7 @@ class MtxSidecarBuildPlugin extends NodejsBuildPlugin {
118
118
  const modelFilePaths = cds.resolve(modelPaths)
119
119
 
120
120
  if (!modelFilePaths || modelFilePaths.length === 0) {
121
- throw new BuildError("No CDS models found in MTX sidecar. Make sure up-to-date versions of the required npm modules are installed.")
121
+ 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.")
122
122
  }
123
123
  DEBUG?.(`sidecar model: ${relativePaths(cds.root, modelFilePaths).join(", ")}`)
124
124
 
@@ -126,7 +126,7 @@ class MtxSidecarBuildPlugin extends NodejsBuildPlugin {
126
126
  const unresolved = resolveRequiredSapModels(modelPaths)
127
127
  if (unresolved.length > 0) {
128
128
  // log error, but don't fail
129
- this.pushMessage(`CDS models [${unresolved.join(', ')}] required by MTX sidecar cannot be resolved. Make sure up-to-date versions of the missing npm modules are installed.`, ERROR)
129
+ 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)
130
130
  }
131
131
 
132
132
  // using synchronous compilation as cds.root and cds.env have been switched to mtx/sidecar
@@ -3,7 +3,7 @@ const path = require('path')
3
3
  const cds = require('../../../cds')
4
4
  const cmd = require('../../../util/command');
5
5
  const EdmxBuildPlugin = require('../edmxBuildPlugin')
6
- const { BuildError, getWorkspaces } = require('../../util')
6
+ const { BuildError, getWorkspaces, findWorkspaceRoot } = require('../../util')
7
7
  const { OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM, ODATA_VERSION_V2, FOLDER_GEN, CONTENT_EDMX, CONTENT_PACKAGELOCK_JSON,
8
8
  CONTENT_NPMRC, CONTENT_CDSRC_JSON, CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, FLAVOR_LOCALIZED_EDMX,
9
9
  OPTION_WS_PACK} = require('../../constants')
@@ -160,14 +160,14 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
160
160
  }
161
161
 
162
162
  async _packWorkspaceDependencies(dest) {
163
- const wsRoot = NodejsBuildPlugin._findWorkspaceRoot(cds.root)
163
+ const wsRoot = findWorkspaceRoot(cds.root)
164
164
  const appPkg = require(path.join(dest, 'package.json'))
165
165
  let workspaces
166
166
  if (this.task.options.workspaces) {
167
167
  workspaces = Array.isArray(this.task.options.workspaces) ? this.task.options.workspaces : [this.task.options.workspaces]
168
168
  } else {
169
169
  // devDependencies are not considered
170
- workspaces = await getWorkspaces(true, false, wsRoot);
170
+ workspaces = await getWorkspaces(wsRoot, true, false);
171
171
  }
172
172
  if (workspaces.length) {
173
173
  let pkgDescriptors
@@ -197,22 +197,6 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
197
197
  }
198
198
  }
199
199
 
200
- static _findWorkspaceRoot(dir) {
201
- try {
202
- if (require(path.join(dir, 'package.json')).workspaces) {
203
- return dir
204
- }
205
- } catch (err) {
206
- // ignore
207
- }
208
- if (fs.existsSync(path.join(dir, '.gitmodules'))) {
209
- return // project root reached
210
- }
211
- if (path.dirname(dir) !== dir) {
212
- return this._findWorkspaceRoot(path.dirname(dir))
213
- }
214
- }
215
-
216
200
  static async _execNpmPack(dest, wsRoot, workspaces) {
217
201
  const args = ['pack', '-ws', '--json', `--pack-destination=${dest}`]
218
202
  workspaces.forEach(w => args.push(`-w=${w}`))
package/lib/build/util.js CHANGED
@@ -7,6 +7,7 @@ const IS_WIN = require('os').platform() === 'win32'
7
7
  const { SEVERITY_ERROR, FILE_EXT_CDS } = require('./constants');
8
8
  const DEBUG = cds.debug('cli|build')
9
9
  const SECRETS = /(passw)|(cert)|(ca)|(secret)|(key)/i
10
+ const WS_MAX_DEPTH = 2
10
11
 
11
12
  function getProperty(src, segments) {
12
13
  segments = Array.isArray(segments) ? segments : segments.split('.')
@@ -98,13 +99,13 @@ function _redacted(cred) {
98
99
  if (!cred) return cred
99
100
  if (Array.isArray(cred)) return cred.map(c => typeof c === 'string' ? '...' : _redacted(c))
100
101
  if (typeof cred === 'object') {
101
- const newCred = Object.assign({}, cred)
102
- Object.keys(newCred).forEach(k => (typeof newCred[k] === 'string' && SECRETS.test(k)) ? (newCred[k] = '...') : (newCred[k] = _redacted(newCred[k])))
103
- return newCred
102
+ const newCred = Object.assign({}, cred)
103
+ Object.keys(newCred).forEach(k => (typeof newCred[k] === 'string' && SECRETS.test(k)) ? (newCred[k] = '...') : (newCred[k] = _redacted(newCred[k])))
104
+ return newCred
104
105
  }
105
106
  return cred
106
107
  }
107
-
108
+
108
109
  /**
109
110
  * Returns a list of fully qualified model names belonging to the '@sap' namespace that cannot be resolved.
110
111
  * E.g. the module might NOT have been installed.
@@ -173,28 +174,30 @@ async function pathExists(path) {
173
174
  return fs.promises.access(path).then(() => true).catch(() => false);
174
175
  }
175
176
 
177
+ async function getWorkspacePaths() {
178
+ const wsRoot = findWorkspaceRoot(cds.root)
179
+ if (!wsRoot) {
180
+ return []
181
+ }
182
+ return (await getWorkspaces(wsRoot)).filter(ws => cds.root !== path.join(wsRoot, ws)).map(ws => path.join(wsRoot, ws))
183
+ }
184
+
176
185
  /**
177
186
  * Returns the list of workspaces defined for the current project. A workspace dependency
178
187
  * needs to be defined resulting in a corresponding sym-link.
179
- * @returns the paths are relative to 'cds.root'.
188
+ * @returns the paths are relative to 'wsRoot'.
180
189
  */
181
- async function getWorkspaces(resolve = false, devDependencies = true, wsRoot = cds.root) {
182
- let pgkJsonWsRoot
183
- try {
184
- pgkJsonWsRoot = require(path.join(wsRoot, 'package.json'))
185
- } catch (e) {
186
- if (e.code === 'MODULE_NOT_FOUND') {
187
- DEBUG?.(`No package.json found in project '${wsRoot}', skipping workspace resolution.`)
188
- return []
189
- }
190
- throw e
190
+ async function getWorkspaces(wsRoot, resolve = false, devDependencies = true) {
191
+ wsRoot ??= findWorkspaceRoot(cds.root)
192
+ if (!wsRoot) {
193
+ return []
191
194
  }
192
-
193
- const workspaces = []
195
+ const pgkJsonWsRoot = require(path.join(wsRoot, 'package.json'))
194
196
  let pgkJson = pgkJsonWsRoot
195
197
  if (wsRoot !== cds.root) {
196
198
  pgkJson = require(path.join(cds.root, 'package.json'))
197
199
  }
200
+ const workspaces = []
198
201
  if (pgkJsonWsRoot.workspaces) {
199
202
  if (resolve) {
200
203
  _validateWsDependencies(wsRoot, pgkJson.dependencies, devDependencies ? pgkJson.devDependencies : {})
@@ -215,6 +218,25 @@ async function getWorkspaces(resolve = false, devDependencies = true, wsRoot = c
215
218
  return workspaces
216
219
  }
217
220
 
221
+ function findWorkspaceRoot(currentPath, depth = 0) {
222
+ if (depth > WS_MAX_DEPTH) {
223
+ DEBUG?.(`No workspaces exist, max depth of ${WS_MAX_DEPTH} reached`)
224
+ return
225
+ }
226
+ const packageJson = path.join(currentPath, 'package.json')
227
+ if (fs.existsSync(packageJson)) {
228
+ if (require(path.join(currentPath, 'package.json')).workspaces) {
229
+ return currentPath
230
+ }
231
+ }
232
+ if (fs.existsSync(path.join(currentPath, '.gitmodules'))) {
233
+ return // project root reached
234
+ }
235
+ if (path.dirname(currentPath) !== currentPath) {
236
+ return findWorkspaceRoot(path.dirname(currentPath), depth + 1)
237
+ }
238
+ }
239
+
218
240
  /**
219
241
  * This JavaScript function filterFeaturePaths is used to filter and categorize file paths
220
242
  * from a given model into two categories: base and features.
@@ -452,7 +474,9 @@ module.exports = {
452
474
  getI18nDefaultFolder,
453
475
  normalizePath,
454
476
  pathExists,
477
+ getWorkspacePaths,
455
478
  getWorkspaces,
479
+ findWorkspaceRoot,
456
480
  filterFeaturePaths,
457
481
  getFiles,
458
482
  BuildMessage,
package/lib/cds.js CHANGED
@@ -6,11 +6,19 @@ module.exports = Object.defineProperties (cds, {
6
6
  // for internal use in cds-dk only
7
7
  _log: { value: _local ('../bin/util/log'), configurable: true },
8
8
  _local: { value: _local, configurable: true },
9
+ _localOrGlobal: { value: id => _local(id, _npmRoot()), configurable: true },
9
10
  })
10
11
 
11
- function _local (id) {
12
+ function _local (id, ...morePaths) {
12
13
  return require(require.resolve (id, {paths:[
13
14
  process.cwd(), // project-local module is preferred
14
- __dirname // otherwise from our own dependencies
15
+ __dirname, // otherwise from our own dependencies
16
+ ...morePaths
15
17
  ]}))
16
18
  }
19
+
20
+ function _npmRoot() {
21
+ try {
22
+ return require ('child_process').execSync('npm root -g').toString().trim()
23
+ } catch { return '' }
24
+ }
@@ -1,5 +1,4 @@
1
1
  const path = require('path');
2
- const util = require('util');
3
2
  const { bold } = require('../../util/term');
4
3
 
5
4
  const HDI_DEPLOY_LIB = '@sap/hdi-deploy';
@@ -40,15 +39,20 @@ module.exports = new class HdiDeployUtil {
40
39
  if (options.use_hdb === false) options.use_hdb = undefined
41
40
  deployerEnv.HDI_DEPLOY_OPTIONS = JSON.stringify(options)
42
41
 
43
- const asyncDeploy = util.promisify(deploy);
44
- const { exitCode, signal } = await asyncDeploy(dbDir, deployerEnv);
45
- if (exitCode) {
46
- let message = `HDI deployment failed with exit code ${exitCode}`;
47
- if (signal) {
48
- message += `. ${signal}`;
49
- }
50
- throw new Error(message);
51
- }
42
+ return await new Promise((resolve, reject) => {
43
+ deploy(dbDir, deployerEnv, (error, response) => {
44
+ if (error) return reject(error)
45
+ if (response?.exitCode) {
46
+ let message = `HDI deployment failed with exit code ${response.exitCode}.`
47
+ if (response.signal) message += `. ${response.signal}`
48
+ return reject(new Error(message))
49
+ }
50
+ return resolve()
51
+ }, {
52
+ stderrCB: buffer => console.error(buffer.toString()),
53
+ stdoutCB: buffer => console.log(buffer.toString())
54
+ })
55
+ })
52
56
  }
53
57
 
54
58
  _loadHdiDeployLib(cwd) {
@@ -28,7 +28,7 @@ const messages = {
28
28
  BOUND_ACTION_COLLISION: 'The combination of Action name and binding parameter type must be unique for Bound Action ',
29
29
  STREAM_NO_COLLECTION: `'Edm.Stream' cannot be of Collection type.`,
30
30
  UNSUPPORTED_PROPERTY_TYPE: 'Property cannot be of EntityType. Check the type: ',
31
- UNRESOLVED_SCHEMA_NAMESPACE: `Could not resolve the schema `,
31
+ UNRESOLVED_SCHEMA_NAMESPACE: `Could not resolve the schema `,
32
32
  INVALID_DATATYPE: `Invalid Data Type `,
33
33
  ELEMENT_NAME_COLLISION: 'Collision between EntityContainer Element Name and Schema Element Name is currently not supported. Please check the element name: ',
34
34
  NO_DEFAULT_FOR_COLLECTIONS: 'WARNING: Collection type cannot have a Default value.',
@@ -38,6 +38,7 @@ const messages = {
38
38
  NULLABLE_KEY: 'WARNING: Key element cannot be Nullable.',
39
39
  COLLISION_DETECTED: `INFO: Element name collision detected. Adding suffix to affected type category.`,
40
40
  SPECIFY_CONFIG: 'For --config option, please specify correct JSON Object.',
41
+ INVALID_ANNOTATION: 'Annotation element must have a Term attribute',
41
42
  };
42
43
 
43
44
  function getMessages() {
@@ -1,4 +1,6 @@
1
1
  'use strict';
2
+
3
+ const message = require("../../message").getMessages();
2
4
 
3
5
  const vocabulariesMap = new Map([
4
6
  ['org.odata.core.v1', () => require('../vocabularies/Org.OData.Core.V1.json')],
@@ -1754,6 +1756,9 @@ class AnnotationExpression extends Expression {
1754
1756
  annotate(result) { super.annotate(this.getTargetPropertyName(), this.getTarget(), result); }
1755
1757
  interpret() {
1756
1758
  const element = this.getElement();
1759
+ if(!Object.hasOwn(element, "attributes") || !Object.hasOwn(element.attributes, "Term")) {
1760
+ throw new Error(message.INVALID_ANNOTATION);
1761
+ }
1757
1762
  let name = element.attributes.Term;
1758
1763
  if (element.attributes.Qualifier) name += '#' + element.attributes.Qualifier;
1759
1764
  name = '@' + name;
@@ -4,10 +4,10 @@
4
4
  */
5
5
 
6
6
  "use strict";
7
- let messages = require("../message").getMessages();
8
- let common = require("../common");
9
- let versionInfo = require("../../../package.json").version;
10
- let Multimap = require("./util/MultiMap");
7
+ const messages = require("../message").getMessages();
8
+ const common = require("../common");
9
+ const versionInfo = require("../../../package.json").version;
10
+ const Multimap = require("./util/MultiMap");
11
11
  const ignoreObjects = ["$Reference"]; // add all the objects that needs to be ignored for parsing.
12
12
  const { warn, info } = require("../../util/term");
13
13
  const MetadataConverterFactory = require("./metadataconverter/MetadataConverterFactory");
@@ -428,6 +428,11 @@ function _parseAnnotationTerms(annotationTerm, annotationTermObject, csnDefsTarg
428
428
  function _validateAnnotationNamespace(annotationTerm, vocabNamespaceToAlias) {
429
429
  let aliasOrNamespace = annotationTerm.substring(1, annotationTerm.lastIndexOf('.'));
430
430
 
431
+ // This check is for ignoring the @cds annotations given for special cases, e.g. @cds.validate = false for collided action/function
432
+ if (aliasOrNamespace === "cds") {
433
+ return
434
+ }
435
+
431
436
  for (let alias of Object.values(known_vocabularies)) {
432
437
  if (alias === aliasOrNamespace) {
433
438
  return;
@@ -1358,29 +1363,6 @@ function _parseEntityContainer(csnDefs, entityContainerElements, parserContext)
1358
1363
  return csnDefs;
1359
1364
  }
1360
1365
 
1361
- /**
1362
- * @param {List<Object>} dataObjList, action/function object list
1363
- * @returns unique parameter list
1364
- */
1365
- function _collectAllParameters(dataObjList) {
1366
- let uniqueParams = [], paramNames = [], params;
1367
-
1368
- for (let dataObj of dataObjList) {
1369
- params = dataObj.$Parameter;
1370
-
1371
- if (params) {
1372
- for (let i = 0; i < params.length; i++) {
1373
- if (!paramNames.includes(params[i].$Name)) {
1374
- paramNames.push(params[i].$Name);
1375
- uniqueParams.push(params[i]);
1376
- }
1377
- }
1378
- }
1379
- }
1380
-
1381
- return uniqueParams;
1382
- }
1383
-
1384
1366
  /**
1385
1367
  * @param {Boolean} isAction, to identify action/function
1386
1368
  * @param {String} fqName, fully qualified name
@@ -1389,7 +1371,7 @@ function _collectAllParameters(dataObjList) {
1389
1371
  *
1390
1372
  * Scenarios:
1391
1373
  * 1. Unbound Actions can't have collision. Action name must be unique.
1392
- * 2. Unbound Functions can have collision; capture the max number of parameters.
1374
+ * 2. Unbound Functions can have collision; we will parse only one function with the same name having no paramter and a generic String return type.
1393
1375
  */
1394
1376
  function _populateUnboundActionFunction(isAction, fqName, schemaDataList, parserContext) {
1395
1377
  let [schemaName, actFunName] = _extractSchemaNameAndElementName(fqName);
@@ -1409,8 +1391,12 @@ function _populateUnboundActionFunction(isAction, fqName, schemaDataList, parser
1409
1391
  }
1410
1392
  else {
1411
1393
  if (dataList.length > 1) {
1412
- actionFunctionObject['$Parameter'] = _collectAllParameters(dataList);
1394
+ actionFunctionObject['$Parameter'] = [];
1413
1395
  parserContext.unboundFunctions[fqName] = actionFunctionObject;
1396
+ parserContext.unboundFunctions[fqName]["@cds.validate"] = false;
1397
+ if(parserContext.unboundFunctions[fqName].$ReturnType){
1398
+ parserContext.unboundFunctions[fqName].$ReturnType.$Type = "Edm.String";
1399
+ }
1414
1400
  }
1415
1401
  else {
1416
1402
  parserContext.unboundFunctions[fqName] = actionFunctionObject;
@@ -1439,12 +1425,11 @@ function _collectUnboundActionFunction(schemaDataList, parserContext) {
1439
1425
  * @param {Object} parserContext
1440
1426
  *
1441
1427
  * Scenarios for bound actions, functions:
1442
- * 1. Actions with same name and binding parameter type can't have collision.
1443
- * 2. Functions can have collision and we will capture the max number of parameters.
1428
+ * 1. Actions with same name and binding parameter type can have collision and we will parse only one action with that name having no paramter and a genric String return type.
1429
+ * 2. Functions can have collision and we will parse only one function with that name having no paramter and a genric String return type.
1444
1430
  * Special scenario is when same function name is bound to different entity as well.
1445
1431
  */
1446
1432
  function _collectBoundActionFunction(isAction, currNamespace, elementName, elementDataList, parserContext) {
1447
- let functionObjList; // list to collect all the collision obj for function
1448
1433
 
1449
1434
  for (let i = 0; i < elementDataList.length; i++) {
1450
1435
  let dataObj = elementDataList[i];
@@ -1480,7 +1465,12 @@ function _collectBoundActionFunction(isAction, currNamespace, elementName, eleme
1480
1465
  parserContext.entityToBoundActions[boundEntityName][elementName] = dataObj;
1481
1466
  }
1482
1467
  else {
1483
- throw new Error(messages.BOUND_ACTION_COLLISION + `${elementName} in entity ${entityName} of schema ${currNamespace}.`);
1468
+ parserContext.entityToBoundActions[boundEntityName][elementName]["@cds.validate"] = false;
1469
+ parserContext.entityToBoundActions[boundEntityName][elementName]["$Parameter"] = [];
1470
+ if (!parserContext.entityToBoundActions[boundEntityName][elementName].$ReturnType) {
1471
+ parserContext.entityToBoundActions[boundEntityName][elementName].$ReturnType = {};
1472
+ }
1473
+ parserContext.entityToBoundActions[boundEntityName][elementName].$ReturnType.$Type = "Edm.String";
1484
1474
  }
1485
1475
  }
1486
1476
  else {
@@ -1488,18 +1478,12 @@ function _collectBoundActionFunction(isAction, currNamespace, elementName, eleme
1488
1478
  if (!parserContext.entityToBoundFunctions[boundEntityName]) {
1489
1479
  parserContext.entityToBoundFunctions[boundEntityName] = {};
1490
1480
  }
1491
- if (!functionObjList || !parserContext.entityToBoundFunctions[boundEntityName][elementName]) {
1492
- functionObjList = [];
1493
- }
1494
1481
  parserContext.entityToBoundFunctions[boundEntityName][elementName] = dataObj;
1495
- functionObjList.push(dataObj);
1496
1482
  }
1497
1483
  else {
1498
- functionObjList.push(dataObj);
1499
- let functionObject = functionObjList[0];
1500
- functionObject['$Parameter'] = _collectAllParameters(functionObjList);
1501
- functionObjList = [functionObject]; // reset the function object list with recently formed object
1502
- parserContext.entityToBoundFunctions[boundEntityName][elementName] = functionObject;
1484
+ parserContext.entityToBoundFunctions[boundEntityName][elementName]["@cds.validate"] = false;
1485
+ parserContext.entityToBoundFunctions[boundEntityName][elementName]["$Parameter"] = [];
1486
+ parserContext.entityToBoundFunctions[boundEntityName][elementName].$ReturnType.$Type = "Edm.String";
1503
1487
  }
1504
1488
  // throw error if the return type is missing
1505
1489
  if (!parserContext.entityToBoundFunctions[boundEntityName][elementName].$ReturnType) {
@@ -1688,7 +1672,12 @@ function _generateEDMX2JSON(edmx, name_space) {
1688
1672
  if (missingReferencedDocuments?.length > 0) {
1689
1673
  console.log(missingReferencedDocuments);
1690
1674
  }
1691
- resolve(json);
1675
+
1676
+ if (error) {
1677
+ reject(error);
1678
+ } else {
1679
+ resolve(json);
1680
+ }
1692
1681
  });
1693
1682
  } catch (err) {
1694
1683
  reject(err);