@sap/cds-dk 8.7.1 → 8.7.3

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.
package/CHANGELOG.md CHANGED
@@ -7,7 +7,31 @@ This project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  The format is based on [Keep a Changelog](https://keepachangelog.com/).
9
9
 
10
- ## Version 8.7.1 - 2024-02-04
10
+ ## Version 8.7.3 - 2025-02-19
11
+
12
+ ### Fixed
13
+
14
+ - `cds compile -o` fixes the output file name in `-o <service>.json`.
15
+ - `cds lint` won't stumble over scalar config objects anymore.
16
+ - `cds init` uses latest Maven Java archetype version 3.7.2 for creating Java projects.
17
+ - `cds deploy --to hana --dry` doesn't exit with a `TypeError` if there are no models.
18
+ - `cds add ias` fixes a few scenarios in combination with multitenancy.
19
+
20
+ ## Version 8.7.2 - 2025-02-14
21
+
22
+ ### Fixed
23
+
24
+ - `cds compile -o` with a file name such as `cds c srv/cat-service.cds -o srv/cat-service.json`.
25
+ - `cds compile` without `-o` doesn't print the file name header for single services any more.
26
+ - `cds add workzone` in combination with multitenancy doesn't throw an error any more.
27
+ - `cds import --from rfc` stores the input file again in `srv/external/<destination>/...` instead of `srv/external/...`.
28
+ - `cds import --name` no longer crashes with a `TypeError`.
29
+ - `cds import --out <filename>` no longer crashes with a `Error: EEXIST`.
30
+ - `BuildError` no longer cuts off its stack.
31
+ - `cds bind -a` doesn't concurrently try to check the Cloud Foundry version or OAuth token.
32
+ - `cds import` now resolves target of association/composition correctly for multiple schema files.
33
+
34
+ ## Version 8.7.1 - 2025-02-04
11
35
 
12
36
  ### Fixed
13
37
 
@@ -217,7 +217,15 @@ async function compile_all (root='.') {
217
217
  }
218
218
 
219
219
  async function compile (models, options={}) {
220
- options.service ??= 'all'
220
+ const { dest } = options
221
+ if (dest) {
222
+ options.service ??= 'all'
223
+ const ext = path.extname(dest)
224
+ if (ext) {
225
+ options.file ??= path.basename(dest, ext)
226
+ options.dest = path.dirname(dest)
227
+ }
228
+ }
221
229
  if (options.all) return compile_all (models[0])
222
230
 
223
231
  const { inspect } = require('util')
@@ -286,7 +294,7 @@ async function compile (models, options={}) {
286
294
  const write = require ('../../lib/util/write')
287
295
  chain = chain.then (write.to ({
288
296
  folder: options.dest,
289
- file: options.file || options.service === 'all' ? path.basename(src) : options.service,
297
+ file: options.file || (options.service === 'all' ? path.basename(src) : options.service),
290
298
  suffix: options.suffix || suffix4(_suffix),
291
299
  [options.dest ? 'foreach' : 'log']: options.log || consoleLog
292
300
  }))
package/bin/import.js CHANGED
@@ -28,7 +28,6 @@ module.exports = Object.assign(_import, {
28
28
 
29
29
  *-o | --out* <filename/dirname>
30
30
  Skips copying to ./srv/external and writes to the specified location.
31
- Dirname is only applicable for importing odata-V4 file with dependencies.
32
31
 
33
32
  *- | --dry*
34
33
  Writes the converted csn to stdout only.
@@ -70,7 +69,7 @@ module.exports = Object.assign(_import, {
70
69
  *-d | --dependencies* <comma-separated list of dependencies>
71
70
  Imports the given list of dependencies along with the main file. Valid for OData V4 only.
72
71
  The dependencies are written to the given dirname(-o option) or ./srv/external.
73
- If the cds files of dependencies are already present in the destination, it will be skipped
72
+ If the cds files of dependencies are already present in the destination, it will be skipped
74
73
  if -f option is not given.
75
74
  NOTE: The dependencies must not have any dependency.
76
75
 
@@ -106,7 +105,7 @@ async function _import(args, options = {}) {
106
105
  const messages = require('../lib/import/message').getMessages();
107
106
  const common = require('../lib/import/common');
108
107
  const cds = require('../lib/index');
109
-
108
+
110
109
  // check if dependencies are provided and import them first
111
110
  if (options.dependencies) {
112
111
  const dependencies = options.dependencies.split(',');
@@ -133,10 +132,11 @@ async function _import(args, options = {}) {
133
132
  input = await common.preProcess(file, options, cwd);
134
133
  }
135
134
 
136
- const fileType = await common.identifyFile(input);
137
- if ((options.isDependency || options.isDependent)
138
- && fileType !== 'edmx') {
139
- throw messages.DEPENDENCY_NOT_SUPPORTED;
135
+ if (input) {
136
+ const fileType = await common.identifyFile(input);
137
+ if ((options.isDependency || options.isDependent) && fileType !== 'edmx') {
138
+ throw messages.DEPENDENCY_NOT_SUPPORTED;
139
+ }
140
140
  }
141
141
 
142
142
  let csn
package/bin/lint.js CHANGED
@@ -1,7 +1,13 @@
1
1
  const Linter = require('../lib/lint/Linter');
2
2
  const linter = new Linter();
3
3
 
4
- module.exports = Object.assign(
5
- linter.lint,
6
- linter.init()
7
- );
4
+ // cds cli exec expects an executable object with various additional properties.
5
+ // As most of the linting functionality is bound to a class instance, we need to wrap the instance
6
+ // to not lose the this-context and attach the additional properties to the function.
7
+ const lint = (...args) => linter.lint(...args)
8
+ const additionalProperties = Object.entries(linter.init())
9
+ .map(([key, value]) => [key, { value }])
10
+
11
+ Object.defineProperties(lint, Object.fromEntries(additionalProperties))
12
+
13
+ module.exports = lint
package/lib/build/util.js CHANGED
@@ -207,7 +207,7 @@ async function getWorkspaces(wsRoot, resolve = false, devDependencies = true) {
207
207
  // read npm workspaces
208
208
  const pkgs = await _execNpmWs(wsRoot)
209
209
  pkgs.forEach(pkg => {
210
- // only create tarball archives for dependencies with version '*'
210
+ // only create tarball archives for dependencies with version '*'
211
211
  if (!resolve || devDependencies && pgkJson.devDependencies?.[pkg] === '*' || pgkJson.dependencies?.[pkg] === '*') {
212
212
  const workspace = _getWorkspace(wsRoot, pkg)
213
213
  if (workspace) {
@@ -240,11 +240,11 @@ function findWorkspaceRoot(currentPath, depth = 0) {
240
240
  }
241
241
 
242
242
  /**
243
- * This JavaScript function filterFeaturePaths is used to filter and categorize file paths
243
+ * This JavaScript function filterFeaturePaths is used to filter and categorize file paths
244
244
  * from a given model into two categories: base and features.
245
245
  * @param {*} model - represents a data model. It's expected to have a $sources property which is an array of file paths.
246
246
  * @param {*} modelPaths - an array of file paths.
247
- * @returns
247
+ * @returns
248
248
  */
249
249
  function filterFeaturePaths(model, modelPaths) {
250
250
  const ftsName = path.dirname(cds.env.features.folders || 'fts/*')
@@ -278,8 +278,8 @@ function filterFeaturePaths(model, modelPaths) {
278
278
  }
279
279
 
280
280
  /**
281
- * Read installed npm workspaces using 'npm ls -ws --json'.
282
- * @returns {Array} list of package paths, or an empty array if either
281
+ * Read installed npm workspaces using 'npm ls -ws --json'.
282
+ * @returns {Array} list of package paths, or an empty array if either
283
283
  * no workspaces are defined or have not been installed.
284
284
  */
285
285
  async function _execNpmWs(wsRoot) {
@@ -331,7 +331,7 @@ function _validateWsDependencies(wsRoot, dependencies, devDependencies) {
331
331
  /**
332
332
  * Returns a flat array of existing files and folders, trailing '/' or '\\' characters have been removed.
333
333
  * @param {Array} modelPaths array of files and folders - optionally nested
334
- * @returns
334
+ * @returns
335
335
  */
336
336
  function _filterModelPaths(modelPaths, filter) {
337
337
  const model = new Set()
@@ -424,7 +424,7 @@ class BuildMessage {
424
424
  * @param {string} message The message text
425
425
  * @param {string} [severity='Error'] Severity: Debug, Info, Warning, Error
426
426
  * @param {any} location Location of the message
427
- *
427
+ *
428
428
  * @memberOf BuildMessage
429
429
  */
430
430
  constructor(message, severity = SEVERITY_ERROR, location) {
@@ -439,9 +439,9 @@ class BuildMessage {
439
439
  }
440
440
 
441
441
  /**
442
- * Class for combined build and compiler errors.
442
+ * Class for combined build and compiler errors.
443
443
  * Additional members:
444
- * messages: vector of detailed build messages
444
+ * messages: array of detailed build messages
445
445
  * @class BuildError
446
446
  * @extends {Error}
447
447
  */
@@ -450,7 +450,6 @@ class BuildError extends Error {
450
450
  super(message)
451
451
  this.name = "BuildError"
452
452
  this.messages = Array.isArray(messages) ? messages : [messages]
453
- this.stack = ""
454
453
  }
455
454
 
456
455
  // for compatibility reasons
@@ -79,6 +79,7 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
79
79
  }
80
80
 
81
81
  if (dry) {
82
+ if (!buildResult) return
82
83
  const sortedResult = Array.from(buildResult.hana).sort();
83
84
  for (const file of sortedResult) {
84
85
  const srcPath = path.join(buildResult.dest, file);
@@ -87,7 +88,7 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
87
88
  console.log();
88
89
  }
89
90
  } else {
90
- // the primary database is always built and deployed
91
+ // the primary database is always built and deployed
91
92
  const dest = path.join(cds.env.build.target, cds.env.folders.db);
92
93
 
93
94
  let serviceName = pServiceName;
@@ -211,8 +212,7 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
211
212
  src: cds.env.folders.db,
212
213
  [OUTPUT_MODE]: OUTPUT_MODE_FILESYSTEM | OUTPUT_MODE_RESULT
213
214
  }
214
- console.log(`Running build`);
215
-
215
+
216
216
  // cds build throws an error if the src folder does not exist
217
217
  const buildResult = await build(buildTaskOptions);
218
218
  return buildResult[0].result;
@@ -444,13 +444,8 @@ function _findPackageJson(dir, cwd) {
444
444
 
445
445
  async function postProcess(filePath, options, csn, cwd) {
446
446
  const services = _getServiceNames(csn);
447
- let destFilePath;
448
-
449
- if (options.isDependency || options.isDependent) {
450
- destFilePath = path.resolve(cwd, options.out ? path.join(options.out, path.basename(filePath)) : path.join(cds.env.folders.srv, 'external', path.basename(filePath)));
451
- } else {
452
- destFilePath = path.resolve(cwd, options.out ? path.join(path.dirname(options.out), path.basename(filePath)) : path.join(cds.env.folders.srv, 'external', path.basename(filePath)));
453
- }
447
+ const out = options.out || path.join(cds.env.folders.srv, 'external');
448
+ let destFilePath = path.resolve(cwd, path.join(out, path.basename(filePath)));
454
449
 
455
450
  if (options.out) {
456
451
  options.out = _validateAndComputeOutputFilePath(options.out, options.as, destFilePath);
@@ -465,7 +460,7 @@ async function postProcess(filePath, options, csn, cwd) {
465
460
  const kind = options.inputFileKind;
466
461
  let result;
467
462
  try {
468
- result = await _getResult(csn, options.as, options.out || destFilePath.replace(/\.[^.]+$/, ''), options.force, kind, options.isDependent);
463
+ result = await _getResult(csn, options.as, options.out || destFilePath.replace(/\.[^.]+$/, ''), options.force, kind, options.isDependent);
469
464
  } catch (error) {
470
465
  if (options.isDependency && error.message === messages.FILE_MODIFIED) {
471
466
  return console.log(info(`[cds] - skipping import of ${path.basename(filePath)} as it is already imported`));
@@ -476,6 +471,9 @@ async function postProcess(filePath, options, csn, cwd) {
476
471
 
477
472
  const [serviceCsn, csnFile] = result;
478
473
 
474
+ // destFilePath might be wrongly computed and contain the csn file name in the path. Fix it here.
475
+ destFilePath = destFilePath.replace(csnFile, path.dirname(csnFile));
476
+
479
477
  if (!options.dry && !options.no_save) {
480
478
  if (cds.env['project-nature'] === 'java') {
481
479
  await _updateJavaConfig(kind, services, csn, options)
@@ -28,7 +28,7 @@ module.exports = Object.assign(cds_import, {
28
28
  */
29
29
  async function cds_import(filepath, options = { keepNamespace: false }) {
30
30
  // identifies the file and calls the respective api
31
- let fileType = await common.identifyFile(filepath);
31
+ let fileType = filepath && await common.identifyFile(filepath);
32
32
 
33
33
  let csn;
34
34
  switch (fileType) {
@@ -37,6 +37,7 @@ module.exports = class ParserContext {
37
37
  this.primarySchema = "";
38
38
  this.schemaAliasToNamespace = {};
39
39
  this.schemaToSchemaDataIndex = {};
40
+ this.schemaToPrimarySchema = {};
40
41
  this.vocabNamespaceToAlias = {};
41
42
  this.entityToBoundActions = {};
42
43
  this.entityToBoundFunctions = {};
@@ -641,11 +641,11 @@ function _parseAssociationProperty(foreignReference, associationNode, parserCont
641
641
  if (entityTypeMapping.length > 1) {
642
642
  for (let j = 0; j < entityTypeMapping.length; j++) {
643
643
  if (entityTypeMapping[j] === foreignReference) {
644
- associationProperty.target = schemaName + '.' + entityTypeMapping[j];
644
+ associationProperty.target = parserContext.schemaToPrimarySchema[schemaName] + '.' + entityTypeMapping[j];
645
645
  }
646
646
  }
647
647
  } else {
648
- associationProperty.target = schemaName + '.' + entityTypeMapping;
648
+ associationProperty.target = parserContext.schemaToPrimarySchema[schemaName] + '.' + entityTypeMapping;
649
649
  }
650
650
  } else {
651
651
  associationProperty.target = resolvedEntityName;
@@ -1554,6 +1554,7 @@ function _preProcess(edmxAsJson, parserContext, importerContext) {
1554
1554
  parserContext.schemaAliasToNamespace[schemaData.$Alias] = schemaName;
1555
1555
  }
1556
1556
  parserContext.schemaToSchemaDataIndex[schemaName] = indexOfSchema;
1557
+ parserContext.schemaToPrimarySchema[schemaName] = parserContext.primarySchema;
1557
1558
  indexOfSchema++;
1558
1559
  }
1559
1560
  // if mock server use case & traversing primary schema, collect relevant mappings
@@ -5,7 +5,7 @@ module.exports = {
5
5
  COMMAND_ADD: 'add',
6
6
 
7
7
  JAVA_LTS_VERSIONS: [17, 21],
8
- MAVEN_ARCHETYPE_VERSION: '3.7.1',
8
+ MAVEN_ARCHETYPE_VERSION: '3.7.2',
9
9
 
10
10
  OPTIONS: Object.freeze({
11
11
  COMPLETION: 'completion',
package/lib/init/merge.js CHANGED
@@ -330,7 +330,7 @@ async function _mergeYAML(into, from, project, semantics = {}) {
330
330
  })
331
331
  .filter(item => !entryMap.get(item))
332
332
  .forEach(item => {
333
- if (!templateEntryMap.get(item)) throw 'Error: did not find entry in template for ' + inspect(item, { colors }) + ' in ' + from
333
+ if (!templateEntryMap.get(item)) throw 'Error: did not find entry in template for ' + inspect(item, { colors, depth:11 }) + ' in ' + from
334
334
  const templateNode = templateEntryMap.get(item).node
335
335
  item.at !== undefined ? targetNode.items.splice(item.at, 0, templateNode) : targetNode.add(templateNode)
336
336
  })
@@ -140,7 +140,7 @@ const requiredMtxSidecarApi4 = srv => ({
140
140
  where: { 'properties.CDS_MULTITENANCY_SIDECAR_URL': '~{mtx-url}' }
141
141
  })
142
142
 
143
- const providedJavaApprouterApi4 = srv => ({
143
+ const requiredJavaApprouterApi4 = srv => ({
144
144
  in: [srv, 'requires'],
145
145
  where: { 'properties.CDS_MULTITENANCY_APPUI_URL': '~{url}' }
146
146
  })
@@ -216,7 +216,7 @@ module.exports = {
216
216
  requiredMtxSidecarApi4,
217
217
  providedAppApi,
218
218
  requiredAppApi4,
219
- providedJavaApprouterApi4,
219
+ requiredJavaApprouterApi4,
220
220
  providedJavaApprouterApi,
221
221
  applicationLogging,
222
222
  approuterMTXRoute,
@@ -12,9 +12,9 @@ modules:
12
12
  requires:
13
13
  - name: {{appName}}-auth
14
14
  {{#isJava}}
15
- - name: app-api
15
+ - name: app-api{{#hasMultitenancy}}
16
16
  properties:
17
- CDS_MULTITENANCY_APPUI_URL: ~{url}
17
+ CDS_MULTITENANCY_APPUI_URL: ~{url}{{/hasMultitenancy}}
18
18
  {{/isJava}}
19
19
 
20
20
  - name: {{appName}}
@@ -6,7 +6,7 @@ const { sort, merge } = require('../../merge')
6
6
  const {
7
7
  srv4, mtxSidecar4, approuter, // Modules
8
8
  xsuaa, // BTP Services
9
- requiredMtxApi, providedJavaApprouterApi4, providedJavaApprouterApi, // APIs
9
+ requiredMtxApi, requiredJavaApprouterApi4, providedJavaApprouterApi, // APIs
10
10
  requiredAppApi4, providedAppApi,
11
11
  approuterMTXRoute,
12
12
  approuterMTXRouteJava // xs-app.json config
@@ -36,7 +36,7 @@ module.exports = class ApprouterTemplate extends require('../../plugin') {
36
36
 
37
37
  if (hasMta) {
38
38
  const srv = srv4(srvPath)
39
- const apis = isJava ? [providedJavaApprouterApi4(srv), providedJavaApprouterApi] : []
39
+ const apis = isJava && hasMultitenancy ? [requiredJavaApprouterApi4(srv), providedJavaApprouterApi] : []
40
40
  const mtxSidecar = mtxSidecar4(isJava ? 'mtx/sidecar' : 'gen/mtx/sidecar')
41
41
  if (isNodejs && hasMultitenancy) apis.push(requiredMtxApi, requiredAppApi4(mtxSidecar), providedAppApi)
42
42
  if (isJava && hasMultitenancy) apis.push(requiredMtxApi)
@@ -36,7 +36,7 @@ modules:
36
36
 
37
37
  - name: {{appName}}-mtx
38
38
  type: nodejs
39
- path: gen/mtx/sidecar
39
+ path: {{#isNodejs}}gen/{{/isNodejs}}mtx/sidecar
40
40
  requires:
41
41
  - name: {{appName}}-auth
42
42
  parameters:
@@ -0,0 +1,12 @@
1
+ {
2
+ "dependencies": {
3
+ "@sap/xssec": "^4"
4
+ },
5
+ "cds": {
6
+ "requires": {
7
+ "auth": {
8
+ "kind": "ias"
9
+ }
10
+ }
11
+ }
12
+ }
@@ -23,9 +23,6 @@ module.exports = class IasTemplate extends require('../../plugin') {
23
23
  const { configFile, isNodejs } = project
24
24
  await merge(__dirname, 'files/package.json.hbs').into(configFile, { project, forceOverwrite: true })
25
25
  if (isNodejs) await sort('package.json', 'dependencies')
26
- if (project.hasMultitenancy) {
27
- await merge(__dirname, 'files/package.json.hbs').into('mtx/sidecar/package.json', { project, forceOverwrite: true })
28
- }
29
26
  // if (project.isJava) await mvn.add('ias')
30
27
  }
31
28
 
@@ -43,6 +40,7 @@ module.exports = class IasTemplate extends require('../../plugin') {
43
40
  conf.cds.profiles.push('subscription-manager')
44
41
  }
45
42
  await write('mtx/sidecar/package.json', conf, { spaces: 2 })
43
+ await merge(__dirname, 'files/sidecar.package.json.hbs').into('mtx/sidecar/package.json')
46
44
  }
47
45
 
48
46
  if (hasMta) {
@@ -92,14 +92,14 @@ resources:
92
92
  applicationType: application
93
93
  appName: {{appName}}-${org}-${space}
94
94
  appCallbacks:
95
- dependenciesCallbacks:
96
- url: ~{mtx-api/mtx-cert-url}/-/cds/sms-provisioning/dependencies/{app_tid}
97
- subscriptionCallbacks:
98
- url: ~{mtx-api/mtx-cert-url}/-/cds/sms-provisioning/tenant/{app_tid}
99
- async:
100
- subscribeEnable: true
101
- unSubscribeEnable: true
102
- timeoutInMillis: 300000 # Increase if your deployments are taking longer than that
95
+ dependenciesCallbacks:
96
+ url: {{#isJava}}~{srv-api/srv-cert-url}/mt/sms/subscriptions/tenants/{app_tid}/dependencies{{/isJava}}{{#isNodejs}}~{mtx-api/mtx-cert-url}/-/cds/sms-provisioning/dependencies/{app_tid}{{/isNodejs}}
97
+ subscriptionCallbacks:
98
+ url: {{#isJava}}~{srv-api/srv-cert-url}/mt/sms/subscriptions/tenants/{app_tid}{{/isJava}}{{#isNodejs}}~{mtx-api/mtx-cert-url}/-/cds/sms-provisioning/tenant/{app_tid}{{/isNodejs}}
99
+ async:
100
+ subscribeEnable: true
101
+ unSubscribeEnable: true
102
+ timeoutInMillis: 300000 # Increase if your deployments are taking longer than that
103
103
  displayName: {{appName}}
104
104
  description: Description for {{appName}}
105
105
  category: Category
@@ -24,11 +24,6 @@ module.exports = class MultitenancyTemplate extends require('../../plugin') {
24
24
  return options?.add?.has('mtx') || options?.add?.has('multitenancy') || !!env.requires?.multitenancy || false
25
25
  }
26
26
 
27
- canRun() {
28
- if (cds.requires.workzone) throw `There is no 'cds add multitenancy' support for SAP BTP WorkZone yet.`
29
- return true
30
- }
31
-
32
27
  async run() {
33
28
  const project = readProject()
34
29
  const { isNodejs, isJava, configFile } = project
@@ -19,13 +19,6 @@ module.exports = class extends require('../../plugin') {
19
19
  return !!env.requires?.workzone || cds.cli.options.add.has('workzone-standard')
20
20
  }
21
21
 
22
- canRun() {
23
- if (cds.requires.multitenancy) {
24
- throw `SAP BTP Work Zone, Standard Edition does not support multitenancy yet.`
25
- }
26
- return true
27
- }
28
-
29
22
  async run() {
30
23
  const project = readProject()
31
24
  const { appUIPaths, appPath } = project