@sap/cds-dk 7.0.2 → 7.1.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 (90) hide show
  1. package/bin/add.js +2 -2
  2. package/bin/bind.js +5 -7
  3. package/bin/build.js +2 -7
  4. package/bin/cds.js +6 -8
  5. package/bin/deploy/to-hana.js +1 -1
  6. package/bin/init.js +2 -2
  7. package/bin/subscribe.js +4 -4
  8. package/bin/unsubscribe.js +4 -4
  9. package/bin/upgrade.js +4 -4
  10. package/lib/bind/cfKeyProvider.js +10 -10
  11. package/lib/bind/index.js +4 -3
  12. package/lib/bind/userCdsrcJsonStorageProvider.js +2 -10
  13. package/lib/build/buildTaskEngine.js +9 -22
  14. package/lib/build/buildTaskFactory.js +18 -27
  15. package/lib/build/constants.js +16 -14
  16. package/lib/build/index.js +12 -3
  17. package/lib/build/provider/buildTaskProviderInternal.js +23 -15
  18. package/lib/build/provider/mtx/index.js +8 -2
  19. package/lib/build/provider/mtx/resourcesTarBuilder.js +2 -6
  20. package/lib/build/provider/mtx-sidecar/index.js +13 -8
  21. package/lib/build/util.js +13 -0
  22. package/lib/client/activate.js +7 -7
  23. package/lib/client/auth_manager.js +5 -5
  24. package/lib/client/base_command.js +7 -10
  25. package/lib/client/extend.js +2 -2
  26. package/lib/client/helper/logging.js +7 -2
  27. package/lib/client/params.js +2 -3
  28. package/lib/client/pull.js +3 -3
  29. package/lib/client/push.js +3 -3
  30. package/lib/client/settings_manager.js +7 -8
  31. package/lib/client/tenant_action.js +3 -3
  32. package/lib/compile/asyncapi/components/messages/index.js +41 -18
  33. package/lib/compile/asyncapi/index.js +23 -2
  34. package/lib/deploy/to-hana/hana.js +1 -2
  35. package/lib/import/common.js +3 -0
  36. package/lib/import/message.js +2 -1
  37. package/lib/import/odata/metadataconverter/MetadataConverter.js +1 -0
  38. package/lib/import/odata/v4parser.js +85 -95
  39. package/lib/init/constants.js +1 -1
  40. package/lib/init/index.js +78 -73
  41. package/lib/init/template/approuter/index.js +3 -4
  42. package/lib/init/template/auditlog/index.js +3 -4
  43. package/lib/init/template/cf-manifest/files/manifest.yml.hbs +15 -23
  44. package/lib/init/template/cf-manifest/files/services-manifest.yml.hbs +22 -17
  45. package/lib/init/template/cf-manifest/index.js +2 -2
  46. package/lib/init/template/connectivity/index.js +2 -3
  47. package/lib/init/template/data/index.js +7 -8
  48. package/lib/init/template/destination/index.js +3 -5
  49. package/lib/init/template/enterprise-messaging/index.js +4 -5
  50. package/lib/init/template/enterprise-messaging-shared/index.js +3 -4
  51. package/lib/init/template/extensibility/index.js +2 -3
  52. package/lib/init/template/file-based-messaging/index.js +2 -2
  53. package/lib/init/template/hana/files/mta.yaml.hbs +1 -1
  54. package/lib/init/template/hana/index.js +39 -63
  55. package/lib/init/template/helm/index.js +15 -14
  56. package/lib/init/template/html5-repo/index.js +3 -7
  57. package/lib/init/template/java/index.js +3 -3
  58. package/lib/init/template/kibana/index.js +3 -4
  59. package/lib/init/template/lint/index.js +13 -13
  60. package/lib/init/template/mta/index.js +3 -2
  61. package/lib/init/template/multitenancy/files/package.sidecar.json.hbs +1 -1
  62. package/lib/init/template/multitenancy/index.js +16 -37
  63. package/lib/init/template/nodejs/index.js +4 -6
  64. package/lib/init/template/pipeline/index.js +3 -4
  65. package/lib/init/template/redis-messaging/index.js +3 -4
  66. package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +1 -1
  67. package/lib/init/template/sample/files/app/browse/webapp/manifest.json +1 -1
  68. package/lib/init/template/sample/files/srv/admin-service.cds +1 -1
  69. package/lib/init/template/sample/files/srv/cat-service.cds +1 -1
  70. package/lib/init/template/sample/index.js +6 -5
  71. package/lib/init/template/sqlite/index.js +1 -1
  72. package/lib/init/template/templateBase.js +9 -19
  73. package/lib/init/template/tiny-sample/index.js +5 -5
  74. package/lib/init/template/toggles/index.js +1 -2
  75. package/lib/init/template/typer/files/xsconfig.json +0 -1
  76. package/lib/init/template/typer/index.js +1 -1
  77. package/lib/init/template/xsuaa/index.js +3 -4
  78. package/lib/init/util/indent.js +57 -0
  79. package/lib/init/util/merge.js +72 -16
  80. package/lib/init/util/mvn.js +40 -31
  81. package/lib/init/util/projectReader.js +52 -168
  82. package/lib/init/util/template.js +12 -11
  83. package/lib/init/util/xml.js +6 -6
  84. package/lib/util/cfUtil.js +1 -2
  85. package/lib/util/pruneAxiosErrors.js +22 -0
  86. package/lib/watch/open.js +6 -3
  87. package/npm-shrinkwrap.json +356 -309
  88. package/package.json +1 -2
  89. package/lib/client/helper/errors.js +0 -26
  90. package/lib/util/version.js +0 -27
@@ -11,7 +11,7 @@
11
11
  "i18n": "i18n/i18n.properties",
12
12
  "dataSources": {
13
13
  "AdminService": {
14
- "uri": "/admin/",
14
+ "uri": "/odata/v4/admin/",
15
15
  "type": "OData",
16
16
  "settings": {
17
17
  "odataVersion": "4.0"
@@ -11,7 +11,7 @@
11
11
  "i18n": "i18n/i18n.properties",
12
12
  "dataSources": {
13
13
  "CatalogService": {
14
- "uri": "/browse/",
14
+ "uri": "/odata/v4/catalog/",
15
15
  "type": "OData",
16
16
  "settings": {
17
17
  "odataVersion": "4.0"
@@ -1,5 +1,5 @@
1
1
  using { sap.capire.bookshop as my } from '../db/schema';
2
- service AdminService @(requires:'admin', path:'/admin') {
2
+ service AdminService @(requires:'admin') {
3
3
  entity Books as projection on my.Books;
4
4
  entity Authors as projection on my.Authors;
5
5
  }
@@ -1,5 +1,5 @@
1
1
  using { sap.capire.bookshop as my } from '../db/schema';
2
- service CatalogService @(path:'/browse') {
2
+ service CatalogService {
3
3
 
4
4
  /** For displaying lists of Books */
5
5
  @readonly entity ListOfBooks as projection on Books
@@ -6,7 +6,7 @@ const { copyFiles } = require('../../util/template')
6
6
  module.exports = class SampleTemplate extends require('../templateBase') {
7
7
 
8
8
  async canRun() {
9
- const { isJava } = await readProject(this.options)
9
+ const { isJava } = await readProject()
10
10
  if (isJava) throw `'cds add sample' is not available for Java yet`
11
11
  return true
12
12
  }
@@ -14,11 +14,12 @@ module.exports = class SampleTemplate extends require('../templateBase') {
14
14
  async run() {
15
15
  const { db, srv, app } = (await this.getEnv()).folders
16
16
  const language = exists('pom.xml') ? 'java' : 'nodejs'
17
+ const { force } = cds.cli.options
17
18
  await Promise.all([
18
- copyFiles(join(__dirname, 'files', 'db'), db, {}, this.options.force),
19
- copyFiles(join(__dirname, 'files', 'app'), app, {}, this.options.force),
20
- copyFiles(join(__dirname, 'files', 'srv'), srv, {}, this.options.force),
21
- copyFiles(join(__dirname, 'files', language), srv, {}, this.options.force)
19
+ copyFiles(join(__dirname, 'files', 'db'), db, {}, force),
20
+ copyFiles(join(__dirname, 'files', 'app'), app, {}, force),
21
+ copyFiles(join(__dirname, 'files', 'srv'), srv, {}, force),
22
+ copyFiles(join(__dirname, 'files', language), srv, {}, force)
22
23
  ])
23
24
  }
24
25
  }
@@ -7,7 +7,7 @@ const { mergeJSON } = require('../../util/merge')
7
7
  module.exports = class SQLiteTemplate extends require('../templateBase') {
8
8
 
9
9
  async run() {
10
- const project = await readProject(this.options)
10
+ const project = await readProject()
11
11
  const { configFile } = project
12
12
  project.shortcut = !(await read(configFile)).cds?.requires?.db?.kind
13
13
  await mergeJSON(configFile, join(__dirname, 'files', `package.json.hbs`), project)
@@ -1,25 +1,14 @@
1
- const cds = require('../../cds');
2
- const path = require('path');
3
-
4
- const { PROJECT_TYPE } = require('../constants');
5
- const { exists } = require('../../cds').utils;
1
+ const cds = require('../../cds')
2
+ const { exists } = require('../../cds').utils
3
+ const { PROJECT_TYPE } = require('../constants')
6
4
 
7
5
  module.exports = class TemplateBase {
6
+
8
7
  /**
9
8
  * @constructor
10
9
  */
11
- constructor(generator) {
12
- if (generator.options?.for && typeof generator.options?.for !== 'string') {
13
- throw 'The --for argument must not be empty.'
14
- }
15
- this.projectPath = cds.root;
16
- this.options = generator.options || {};
17
- this.generator = generator;
10
+ constructor() {
18
11
  this.name = this.constructor.name.replace(/template/i, '').replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
19
-
20
- this.cwd = this.options.cwd || process.cwd();
21
-
22
- this.projectName = path.basename(cds.root);
23
12
  }
24
13
 
25
14
  /**
@@ -51,7 +40,7 @@ module.exports = class TemplateBase {
51
40
  }
52
41
 
53
42
  // eslint-disable-next-line no-unused-vars
54
- static hasFacet(env, options) {
43
+ static hasFacet(env) {
55
44
  return true;
56
45
  }
57
46
 
@@ -85,13 +74,14 @@ module.exports = class TemplateBase {
85
74
  }
86
75
 
87
76
  /**
77
+ * @abstract
88
78
  * Executes steps for related facets that are set up for the project.
89
79
  */
90
80
  async runRelatedMerging() {
91
81
  for (const facet of this.getRelatedFacets()) {
92
82
  const Template = require('./'+facet)
93
- const template = new Template(this.generator)
94
- if (Template.hasFacet(await this.getEnv(), this.options)) await template.runDependentMerging()
83
+ const template = new Template()
84
+ if (Template.hasFacet(await this.getEnv())) await template.runDependentMerging()
95
85
  }
96
86
  }
97
87
 
@@ -16,11 +16,11 @@ module.exports = class TinySampleTemplate extends require('../templateBase') {
16
16
  }
17
17
 
18
18
  async _addJavaSamples() {
19
- if (!this.options.add.has(JAVA)) {
20
- const mvnAddSamplesArgs = mvnArchetypes.getAddSamplesCmdArgs(this.options)
19
+ if (!cds.cli.options.add.has(JAVA)) {
20
+ const mvnAddSamplesArgs = mvnArchetypes.getAddSamplesCmdArgs()
21
21
  try {
22
22
  await commandUtil.spawnCommand('mvn', mvnAddSamplesArgs, { cwd: cds.root })
23
- const mvnAddTestsArgs = mvnArchetypes.getAddTestsCmdArgs(this.options)
23
+ const mvnAddTestsArgs = mvnArchetypes.getAddTestsCmdArgs()
24
24
  await commandUtil.spawnCommand('mvn', mvnAddTestsArgs, { cwd: cds.root })
25
25
  } catch (err) {
26
26
  if (err.code === 'ENOENT' && err.path === 'mvn') {
@@ -33,7 +33,7 @@ module.exports = class TinySampleTemplate extends require('../templateBase') {
33
33
 
34
34
  async _addNodejsSamples() {
35
35
  const { db, srv } = (await this.getEnv()).folders
36
- await copyFiles(join(__dirname, 'files', 'nodejs', 'db'), db, {}, this.options.force)
37
- await copyFiles(join(__dirname, 'files', 'nodejs', 'srv'), srv, { dbFolder: db.replace(/[\\/]+$/, '') }, this.options.force)
36
+ await copyFiles(join(__dirname, 'files', 'nodejs', 'db'), db, {}, cds.cli.options.force)
37
+ await copyFiles(join(__dirname, 'files', 'nodejs', 'srv'), srv, { dbFolder: db.replace(/[\\/]+$/, '') }, cds.cli.options.force)
38
38
  }
39
39
  }
@@ -9,10 +9,9 @@ module.exports = class TogglesTemplate extends require('../templateBase') {
9
9
  }
10
10
 
11
11
  async run() {
12
- const project = await readProject(this.options)
12
+ const project = await readProject()
13
13
  const { isNodejs , configFile} = project
14
14
  await mergeJSON(configFile, join(__dirname, 'files', 'package.json.hbs'), project)
15
15
  if (isNodejs) await sort('package.json', 'dependencies')
16
- await this.runDependentMerging()
17
16
  }
18
17
  }
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "checkJs": true,
4
3
  "moduleResolution": "nodenext",
5
4
  "paths": {
6
5
  "#cds-models/*": ["./@cds-models/*"]
@@ -7,7 +7,7 @@ const { exists, read } = require('../../../cds').utils
7
7
 
8
8
  module.exports = class TyperTemplate extends require('../templateBase') {
9
9
  async canRun() {
10
- const { isJava } = await readProject(this.options)
10
+ const { isJava } = await readProject()
11
11
  if (isJava) {
12
12
  console.warn(warn(`This appears to be a Java project. 'cds add ${TYPER}' is intended to be used in Node.js projects. Proceeding anyway.\n`))
13
13
  }
@@ -17,15 +17,14 @@ module.exports = class XsuaaTemplate extends require('../templateBase') {
17
17
  }
18
18
 
19
19
  async run() {
20
- const project = await readProject(this.options)
20
+ const project = await readProject()
21
21
  const { configFile } = project
22
22
  project.shortcut = !(await read(configFile)).cds?.requires?.auth?.kind
23
23
  await mergeJSON(configFile, join(__dirname, 'files', 'package.json.hbs'), project, { forceOverwrite: true })
24
- await this.runDependentMerging()
25
24
  }
26
25
 
27
26
  async runDependentMerging( context = {}) {
28
- const project = await readProject(this.options)
27
+ const project = await readProject()
29
28
  const { isMultitenant, hasMta, hasHelm, isJava, srvPath } = project
30
29
 
31
30
  const cds = require('../../../../lib')
@@ -71,7 +70,7 @@ module.exports = class XsuaaTemplate extends require('../templateBase') {
71
70
 
72
71
  // copy service-instance chart
73
72
  const HelmTemplate = require(`../helm`);
74
- const helmTemplate = new HelmTemplate(this.generator, context.oldTrackingData, context.newTrackingData);
73
+ const helmTemplate = new HelmTemplate(context.oldTrackingData, context.newTrackingData);
75
74
  isIndependentCommand = isIndependentCommand | await helmTemplate.addSubChart('service-instance');
76
75
 
77
76
  // add dependency entry in chart.yaml
@@ -0,0 +1,57 @@
1
+ const indentRegEx = new RegExp(/^(\s*)/)
2
+ const leadingSpacesRegEx = new RegExp(/^( +)/)
3
+ const leadingTabsRegEx = new RegExp(/^(\t+)/)
4
+
5
+ /**
6
+ * Greatest Common Divisor Euclidean algorithm
7
+ * @param {number[]} xs - list of number to find the gcd of.
8
+ * @returns {number} - greatest common divisor of all numbers in xs.
9
+ */
10
+ function gcd (xs) {
11
+ const euclid = (a, b) => b === 0 ? a : euclid(b, a % b)
12
+ const [a, b, ...tail] = xs
13
+ return tail.length
14
+ ? gcd([euclid(a, b)].concat(tail))
15
+ : euclid(a, b ?? 0) // coalesce in case xs consists of exactly one element
16
+ }
17
+
18
+ /**
19
+ * Detects indent used for a file.
20
+ * The algorithm inspects the indentation of every line to find whether tabs or spaces are more prevalent.
21
+ * Once that has been determined, it derives the spacing by calculating the gcd of all relevant lines.
22
+ * I.e. if there are lines indented with 2, 4, or 8 spaces, it will find ' ' as indent.
23
+ * If lines are indented using 1 or 2 tabs, '\t' will be returned.
24
+ * TODO: filter out outliers, i.e. if the user used three spaces in one line, should ' ' space be used for spacing? Or should that be ignored?
25
+ * @param {string[]} file - lines of a file.
26
+ * @returns {string | null} - the string used to space (e.g. 2 spaces, 4 spaces, 1 tab, etc.) or null if the file was empty.
27
+ */
28
+ function detectIndent (file) {
29
+ if (file.length === 0) return null
30
+ if (file.length === 1) return indentRegEx.exec(file[0])[0]
31
+
32
+ const indents = file
33
+ .map(line => indentRegEx.exec(line)[0])
34
+ .filter(Boolean)
35
+ .reduce((bins, indent) => {
36
+ const type = leadingSpacesRegEx.test(indent)
37
+ ? 'spaces'
38
+ : leadingTabsRegEx.test(indent)
39
+ ? 'tabs'
40
+ : undefined
41
+ if (type) {
42
+ const length = indent.split('').length
43
+ const bin = bins[type]
44
+ bin.total++
45
+ bin.indents[length] = (bin.indents[length] ?? 0) + 1
46
+ }
47
+ return bins
48
+ }, { tabs: { indents: {}, total: 0, character: '\t' }, spaces: { indents: {}, total: 0, character: ' ' } })
49
+
50
+ const preferred = indents.tabs.total > indents.spaces.total ? indents.tabs : indents.spaces
51
+ const width = gcd(Object.keys(preferred.indents)) ?? 2
52
+ return preferred.character.repeat(width)
53
+ }
54
+
55
+ module.exports = {
56
+ detectIndent
57
+ }
@@ -2,7 +2,7 @@ const YAML = require('@sap/cds-foss').yaml
2
2
  const { copy, read, write, exists } = require('../../cds').utils
3
3
  const { readYAML, writeYAML, readJSON } = require('../../util/fs')
4
4
  const { EOL } = require('node:os')
5
-
5
+ const { detectIndent } = require('../util/indent')
6
6
 
7
7
  function _isObject(item) {
8
8
  return item && typeof item === 'object' && !Array.isArray(item)
@@ -53,37 +53,93 @@ async function mergeGitignore (into, from) {
53
53
  return enhanced
54
54
  }
55
55
 
56
+ /**
57
+ * @typedef {Object} MatchedNode
58
+ * @property {string | MatchedNode} in - key path or matched node to start search from
59
+ * @property {Object[]} where - list of constraints
60
+ * @property {string} where[].property - property key path
61
+ * @property {string} where[].isEqualTo - comparison string
62
+ */
63
+
64
+ /**
65
+ * @typedef {Object} Overwrites
66
+ * @property {(string | MatchedNode)[]} item - path to item to be overwritten
67
+ * @property {string} withValue - replacement string
68
+ */
69
+
70
+ /**
71
+ * @typedef {Object} Deletion
72
+ * @property {MatchedNode} item - matched node to delete
73
+ * @property {Object[]} relationships - list of relationships to delete
74
+ * @property {string} relationships[].removeProperty - key path to to-be-removed property
75
+ * @property {string[]} relationships[].allWithin - key path to relationship
76
+ */
77
+
78
+ /**
79
+ * @typedef {Object} Relationship
80
+ * @property {(string | MatchedNode)[]} insert - source path to matched node
81
+ * @property {(string | MatchedNode)[]} into - target path for inserted relationship
82
+ */
83
+
84
+ /**
85
+ * @typedef {Object} MergingSemantics
86
+ * @property {boolean} [forceOverwrite] - overwrite values for existing keys
87
+ * @property {MatchedNode[]} [additions] - matched nodes to be added from source template
88
+ * @property {Overwrites[]} [overwrites] - matched keys to be overwritten with given string
89
+ * @property {Deletion[]} [deletions] - matched nodes to be deleted
90
+ * @property {Relationship[]} [relationships] - relationships to be inserted
91
+ */
92
+
93
+ /**
94
+ * @param {string | JSON} into - file path or JSON to merge into
95
+ * @param {string | JSON} from - file path or JSON to merge from
96
+ * @param {import('./projectReader').readProject} [project] - project descriptor used for Mustache replacements
97
+ * @param {MergingSemantics} [semantics] - merging algorithm semantics
98
+ * @returns {Promise<JSON>} - merged JSON
99
+ */
56
100
  async function mergeJSON(into, from, project, semantics) {
57
101
  const source = typeof from === 'string' ? await readJSON(from, project) : from
58
102
 
59
- if (typeof into === 'string' && !exists(into)) {
60
- return write(into, source, { spaces: 2 })
61
- }
62
-
63
103
  let result
64
- if (semantics) {
104
+ if (typeof into === 'string' && !exists(into)) {
105
+ result = source
106
+ } else if (semantics) {
65
107
  const target = await read(into)
66
108
  const targetYAML = YAML.parseDocument(YAML.stringify(target))
67
109
  const sourceYAML = YAML.parseDocument(YAML.stringify(source))
68
110
  const resultYAML = await mergeYAML(targetYAML, sourceYAML, project, semantics)
69
111
  result = YAML.parse(YAML.stringify(resultYAML))
70
112
  } else {
71
- const target = typeof into === 'string' ? await read(into) : into
113
+ const target = typeof into === 'string' ? await read(into, 'json') : into
72
114
  result = _deepMerge(target, source)
73
115
  }
74
- if (typeof into === 'string') await write(into, result, { spaces: 2 })
116
+ // we need to stringify result on site to be able to apply the appropriate indentation.
117
+ // cds-utils.write is currently hardcoded to use spaces for indentation.
118
+ if (typeof into === 'string') {
119
+ const indent = exists(into) && detectIndent((await read(into, 'utf-8')).split(EOL)) || ' '
120
+ await write(into, JSON.stringify(result, null, indent) + EOL)
121
+ }
75
122
  return result
76
123
  }
77
124
 
125
+ /**
126
+ * @param {string | YAML.Document} into - file path or YAML to merge into
127
+ * @param {string | YAML.Document} from - file path or YAML to merge from
128
+ * @param {Object} [project] - project descriptor used for Mustache replacements
129
+ * @param {MergingSemantics} [semantics] - merging algorithm semantics
130
+ * @returns {Promise<YAML.Document>} - merged YAML
131
+ * @throws {Error} Throws an error if empty target YAML is passed in
132
+ */
78
133
  async function mergeYAML(into, from, project, semantics = {}) {
79
134
  const target = typeof into === 'string' ? await readYAML(into) : into instanceof YAML.Document ? into : new YAML.Document(into)
80
135
  const source = typeof from === 'string' ? await readYAML(from, project) : from instanceof YAML.Document ? from : new YAML.Document(from)
81
136
 
82
- if (!target || !target.contents) {
137
+ if (!target?.contents) {
83
138
  if (typeof into === 'string' && typeof from === 'string') {
84
- return project ? writeYAML(into, source) : copy(from, into)
139
+ project ? await writeYAML(into, source) : await copy(from, into)
140
+ return source
85
141
  }
86
- return /* has no YAML */
142
+ throw new Error(`Target YAML doesn't exist`)
87
143
  }
88
144
 
89
145
  const entryMap = new Map, templateEntryMap = new Map()
@@ -148,7 +204,7 @@ async function mergeYAML(into, from, project, semantics = {}) {
148
204
 
149
205
  if (node.items) {
150
206
  if (YAML.isSeq(node) && templateNode?.items) {
151
- actions?.mergeCollection && actions.mergeCollection(node, templateNode)
207
+ actions.mergeCollection?.(node, templateNode)
152
208
  _traverseYAMLCollection(node, actions, templateNode)
153
209
  } else {
154
210
  _traverseYAMLCollection(node, actions, templateNode)
@@ -194,12 +250,12 @@ async function mergeYAML(into, from, project, semantics = {}) {
194
250
  const targetNode = collection.items?.[i]
195
251
  if (targetNode) {
196
252
  _traverseYAMLNode(targetNode, templateIndex, actions, templateNode)
197
- } else if (actions.mergePair) {
198
- actions.mergePair(collection, templateNode)
253
+ } else {
254
+ actions.mergePair?.(collection, templateNode)
199
255
  }
200
256
  } else if (YAML.isMap(templateNode)) {
201
257
  const targetNode = collection.items[templateIndexToIndex.get(templateIndex)]
202
- if (actions.mergeCollection) actions.mergeCollection(targetNode, templateNode)
258
+ actions.mergeCollection?.(targetNode, templateNode)
203
259
  if (targetNode) {
204
260
  _traverseYAMLNode(targetNode, templateIndex, actions, templateNode)
205
261
  }
@@ -266,7 +322,7 @@ async function mergeYAML(into, from, project, semantics = {}) {
266
322
  .filter(addition => {
267
323
  const keyPath = typeof addition.in === 'string' ? addition.in : Array.isArray(addition.in) && typeof addition.in[0] === 'string' ? addition.in[0] : addition.in[1]
268
324
  const keys = keyPath.split('.')
269
- if (keys.length > collectionStack.length + 1) return
325
+ if (keys.length > collectionStack.length + 1) return false
270
326
  if (keys.length > 1) parent = collectionStack[keys.length]
271
327
  return _getYAMLProperty(parent, keyPath) === targetNode
272
328
  })
@@ -1,13 +1,11 @@
1
1
 
2
- const { MAVEN_ARCHETYPE_VERSION, OPTIONS: { JAVA_MVN, JAVA_PACKAGE, SAMPLES, HANA }, REGEX_JAVA_PACKAGE } = require('../constants');
3
-
4
- const { CliError } = require('../../client/helper/errors');
5
-
2
+ const { URLS, MAVEN_ARCHETYPE_VERSION, OPTIONS: { JAVA_MVN, JAVA_PACKAGE, SAMPLES, HANA }, REGEX_JAVA_PACKAGE } = require('../constants');
6
3
 
7
4
  const cp = require('child_process');
8
5
  const util = require('util');
9
6
  const execAsync = util.promisify(cp.exec);
10
-
7
+ const term = require('../../util/term');
8
+ const cmd = require('../../util/command');
11
9
 
12
10
  const DEFAULT_GROUP_ID = 'customer';
13
11
 
@@ -15,7 +13,6 @@ const LOG = console
15
13
  const cds = require('../../cds')
16
14
  const DEBUG = cds.debug('cli')
17
15
 
18
-
19
16
  class MvnArchetypeUtil {
20
17
 
21
18
  _scanMvnParams(mvnParams, params) {
@@ -51,11 +48,12 @@ class MvnArchetypeUtil {
51
48
  return Array.from(params, ([arg, val]) => val !== null && val !== undefined ? `${arg}=${val}` : arg)
52
49
  }
53
50
 
54
- getAddSamplesCmdArgs(options) {
51
+ getAddSamplesCmdArgs() {
55
52
  const params = new Map();
56
53
 
57
54
  params.set(`com.sap.cds:cds-maven-plugin:${MAVEN_ARCHETYPE_VERSION}:addSample`);
58
55
 
56
+ const { options } = cds.cli
59
57
  if (options[JAVA_MVN]) {
60
58
  this._scanMvnParams(options[JAVA_MVN], params);
61
59
  }
@@ -67,11 +65,12 @@ class MvnArchetypeUtil {
67
65
  return this._finalize(params);
68
66
  }
69
67
 
70
- getAddTestsCmdArgs(options) {
68
+ getAddTestsCmdArgs() {
71
69
  const params = new Map();
72
70
 
73
71
  params.set(`com.sap.cds:cds-maven-plugin:${MAVEN_ARCHETYPE_VERSION}:addIntegrationTest`);
74
72
 
73
+ const { options } = cds.cli
75
74
  if (options[JAVA_MVN]) {
76
75
  this._scanMvnParams(options[JAVA_MVN], params);
77
76
  }
@@ -83,14 +82,15 @@ class MvnArchetypeUtil {
83
82
  return this._finalize(params);
84
83
  }
85
84
 
86
- getAddHanaCmdArgs(options) {
85
+ getAddHanaCmdArgs() {
87
86
  const params = new Map();
88
87
 
89
88
  params.set(`com.sap.cds:cds-maven-plugin:${MAVEN_ARCHETYPE_VERSION}:addTargetPlatform`);
90
89
  params.set(`-DtargetPlatform`, `cloudfoundry`);
91
90
 
92
- if (options[JAVA_MVN]) {
93
- this._scanMvnParams(options[JAVA_MVN], params);
91
+ const javaMvn = cds.cli.options[JAVA_MVN]
92
+ if (javaMvn) {
93
+ this._scanMvnParams(javaMvn, params);
94
94
  }
95
95
 
96
96
  return this._finalize(params);
@@ -100,7 +100,7 @@ class MvnArchetypeUtil {
100
100
  return packageName.toLowerCase().replace(/-/g, '_').replace(/\.([0-9])/g, '._$1').replace(/^([0-9])/, '_$1').replace(/\.+/g, '.');
101
101
  }
102
102
 
103
- async getGenerateCmdArgs(projectName, options) {
103
+ async getGenerateCmdArgs(projectName, options = cds.cli.options) {
104
104
  const params = new Map();
105
105
 
106
106
  // set header
@@ -129,29 +129,27 @@ class MvnArchetypeUtil {
129
129
 
130
130
  // -DjdkVersion=[11|17]
131
131
  if (!params.get('-DjdkVersion')) {
132
+ let javaVersion
132
133
  try {
133
- const javaVersion = await execAsync('java -version');
134
- const match = javaVersion?.stderr?.match(/version\s*"?(\d+\.\d+\.\d+)"?/);
135
- if (match?.[1]) {
136
- const segments = match[1]?.split('.');
137
- const version = new Number(segments?.[0]);
138
- switch (true) {
139
- case (version < 17):
140
- if (process.env.WS_BASE_URL) {
141
- throw new CliError(`Java version ${match[1]} is not supported. Use Java 17 or higher. You can use command 'Java: Set Default JDK' to switch to Java 17.`)
142
- }
143
- throw new CliError(`Java version ${match[1]} is not supported. Use Java 17 or higher.`)
144
- case (version < 20): params.set(`-DjdkVersion`, 17); break;
145
- case (version >= 20): params.set(`-DjdkVersion`, 20); break;
146
- default:
147
- }
148
- }
134
+ javaVersion = await execAsync('java -version');
149
135
  } catch (err) {
150
- if (err instanceof CliError) {
151
- throw err;
152
- }
153
136
  DEBUG?.(err);
154
137
  }
138
+ const match = javaVersion?.stderr?.match(/version\s*"?(\d+\.\d+\.\d+)"?/);
139
+ if (match?.[1]) {
140
+ const segments = match[1]?.split('.');
141
+ const version = new Number(segments?.[0]);
142
+ switch (true) {
143
+ case (version < 17):
144
+ if (process.env.WS_BASE_URL) {
145
+ throw `Java version ${match[1]} is not supported. Use Java 17 or higher. You can use command 'Java: Set Default JDK' to switch to Java 17.`
146
+ }
147
+ throw `Java version ${match[1]} is not supported. Use Java 17 or higher.`
148
+ case (version < 20): params.set(`-DjdkVersion`, 17); break;
149
+ case (version >= 20): params.set(`-DjdkVersion`, 20); break;
150
+ default:
151
+ }
152
+ }
155
153
  }
156
154
 
157
155
  if (options[JAVA_PACKAGE]) {
@@ -184,6 +182,17 @@ class MvnArchetypeUtil {
184
182
  artifactId
185
183
  };
186
184
  }
185
+
186
+ async execMvn(args) {
187
+ try {
188
+ await cmd.spawnCommand('mvn', args, { cwd: cds.root });
189
+ } catch (err) {
190
+ if (err.code === 'ENOENT' && err.path === 'mvn') {
191
+ throw `Maven executable 'mvn' not found, follow ${term.info(URLS.MAVEN_INSTALL_HELP)} and install Maven on your machine.`;
192
+ }
193
+ throw err;
194
+ }
195
+ }
187
196
  }
188
197
 
189
198
  module.exports = new MvnArchetypeUtil();