@sap/cds-dk 6.2.3 → 6.3.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 (70) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/bin/bind.js +4 -4
  3. package/bin/deploy/to-h2.js +4 -0
  4. package/bin/deploy/to-sqlite.js +6 -4
  5. package/bin/help.js +1 -1
  6. package/bin/login.js +1 -0
  7. package/bin/push.js +29 -7
  8. package/lib/bind/userCdsrcJsonStorageProvider.js +4 -4
  9. package/lib/client/activate.js +1 -1
  10. package/lib/client/base_command.js +10 -10
  11. package/lib/client/helper/dependency_helper.js +10 -1
  12. package/lib/client/helper/logging.js +6 -2
  13. package/lib/client/pull.js +6 -6
  14. package/lib/client/push.js +33 -14
  15. package/lib/client/settings_manager.js +52 -21
  16. package/lib/compile/openapi/csdl2openapi.js +1 -1
  17. package/lib/compile/to-xsuaa.js +1 -1
  18. package/lib/import/odata/v2parser.js +37 -1
  19. package/lib/import/odata/v4parser.js +101 -23
  20. package/lib/init/constants.js +1 -1
  21. package/lib/init/index.js +23 -45
  22. package/lib/init/template/approuter/index.js +44 -48
  23. package/lib/init/template/auditlog/index.js +14 -10
  24. package/lib/init/template/cf-manifest/index.js +9 -11
  25. package/lib/init/template/connectivity/index.js +10 -10
  26. package/lib/init/template/data/index.js +20 -19
  27. package/lib/init/template/destinations/index.js +14 -9
  28. package/lib/init/template/enterprise-messaging/index.js +14 -9
  29. package/lib/init/template/extensibility/index.js +14 -9
  30. package/lib/init/template/hana/index.js +51 -57
  31. package/lib/init/template/helm/index.js +27 -59
  32. package/lib/init/template/helm/subchart/templates/_helpers.tpl +38 -0
  33. package/lib/init/template/helm/subchart/templates/deployment.yaml +12 -12
  34. package/lib/init/template/helm/subchart/values.md +565 -194
  35. package/lib/init/template/helm/subchart/values.schema.json +163 -8
  36. package/lib/init/template/helm/subchart/values.yaml +17 -1
  37. package/lib/init/template/html5-repo/index.js +15 -11
  38. package/lib/init/template/java/index.js +15 -11
  39. package/lib/init/template/kibana-logging/index.js +30 -31
  40. package/lib/init/template/lint/index.js +17 -18
  41. package/lib/init/template/mta/index.js +18 -40
  42. package/lib/init/template/mtx/index.js +2 -3
  43. package/lib/init/template/multitenancy/index.js +27 -20
  44. package/lib/init/template/nodejs/index.js +18 -20
  45. package/lib/init/template/notebook/index.js +5 -434
  46. package/lib/init/template/pipeline/index.js +10 -10
  47. package/lib/init/template/samples/index.js +7 -16
  48. package/lib/init/template/templateBase.js +17 -18
  49. package/lib/init/template/toggles/files/cdsrc.json +5 -0
  50. package/lib/init/template/toggles/files/cdsrc.json.hbs +7 -0
  51. package/lib/init/template/toggles/index.js +12 -6
  52. package/lib/init/template/xsuaa/index.js +44 -41
  53. package/lib/init/types.d.ts +0 -59
  54. package/lib/init/util/commandUtil.js +6 -7
  55. package/lib/init/util/fsUtil.js +23 -109
  56. package/lib/init/util/merge.js +319 -0
  57. package/lib/init/util/mvnArchetypeUtil.js +6 -5
  58. package/lib/init/util/projectReader.js +125 -300
  59. package/lib/init/util/spinner.js +10 -8
  60. package/lib/init/util/templateUtil.js +25 -368
  61. package/lib/lint/Linter.js +2 -3
  62. package/lib/lint/checks.js +0 -1
  63. package/lib/lint/io.js +18 -34
  64. package/npm-shrinkwrap.json +102 -239
  65. package/package.json +3 -3
  66. package/lib/init/template/notebook/cap_magic.py +0 -118
  67. package/lib/init/template/notebook/files/{{{name}}}.ipynb.hbs +0 -90
  68. package/lib/init/template/notebook/files/{{{name}}}.json +0 -102
  69. package/lib/init/template/notebook/notebook.js +0 -394
  70. package/lib/init/util/logger.js +0 -42
@@ -1,8 +1,7 @@
1
1
  const path = require('path')
2
- const { nullLogger } = require('../util/logger')
3
2
  const cds = require('../../cds')
4
3
  const { BuildTaskFactory } = cds.build
5
- const { fs } = cds.utils
4
+ const { read, exists } = cds.utils
6
5
  const P_LANGUAGE_JAVA = 'java'
7
6
  const P_LANGUAGE_NODEJS = 'nodejs'
8
7
  const DEFAULT_P_LANGUAGE = P_LANGUAGE_NODEJS
@@ -14,11 +13,13 @@ const BUILD_TASK_HANA = "hana"
14
13
  const BUILD_TASK_FIORI = "fiori"
15
14
  const BUILD_TASK_MTX = "mtx"
16
15
  const BUILD_TASK_MTX_SIDECAR = "mtx-sidecar"
16
+ const DEBUG = /\b(cli)\b/.test(process.env.DEBUG) && console.debug
17
+ const LOG = console
17
18
 
18
19
  module.exports = class ProjectReader {
19
20
 
20
- constructor(template) {
21
- this.template = template
21
+ constructor(projectPath) {
22
+ this.projectPath = projectPath
22
23
  }
23
24
 
24
25
  /**
@@ -28,104 +29,59 @@ module.exports = class ProjectReader {
28
29
  const cdsEnvVar = process.env.CDS_ENV
29
30
  process.env.CDS_ENV = profile
30
31
  try {
31
- return await this.template.getEnv();
32
+ return cds.env.for('cds', exists(this.projectPath) ? this.projectPath : this.cwd);
32
33
  } finally {
33
34
  cdsEnvVar ? process.env.CDS_ENV = cdsEnvVar : delete process.env.CDS_ENV
34
35
  }
35
36
  }
36
37
 
37
- get fsUtil() {
38
- return this.template.fsUtil
39
- }
40
-
41
- get logger() {
42
- return this.template.logger
43
- }
44
-
45
- get projectPath() {
46
- return this.template.projectPath
47
- }
48
-
49
38
  async read(options) {
50
39
  const env = await this.getEnv();
51
- this.logger.isDebug && this.logger.debug(JSON.stringify(env, null, 1))
52
-
53
- const capDescriptor = await this._getCapDescriptor(env, options)
54
- const uiDescriptor = await this._getUiDescriptor(env)
55
- const projectDescriptor = {
56
- cap: capDescriptor,
57
- ui: uiDescriptor
58
- }
40
+ DEBUG && DEBUG({ env })
59
41
 
60
- await this._validate(projectDescriptor)
42
+ const cap = await this._getCapDescriptor(env, options)
43
+ const ui = await this._getUiDescriptor(env)
44
+ const projectDescriptor = { cap, ui }
45
+ this._validateDbFolders(cap)
61
46
 
62
- this.logger.isDebug && this.logger.debug(JSON.stringify(projectDescriptor, null, 1))
47
+ DEBUG && DEBUG({ projectDescriptor })
63
48
  return projectDescriptor
64
49
  }
65
50
 
51
+ _validateDbFolders(cap) {
52
+ const { root, db: dbs, isJava } = cap
53
+ if (isJava) {
54
+ for (const db of dbs) if (!exists(path.join(root, db.path, 'package.json'))) {
55
+ LOG.error(`Missing 'package.json' in folder ${db.path} - 'cds add hana' adds configuration for SAP HANA`)
56
+ }
57
+ }
58
+ }
59
+
66
60
  async _getCapDescriptor(env, options) {
67
61
  const projectPath = this.projectPath
68
- let capDescriptor = {
69
- for: options && options.for,
62
+ const _hasFacet = template => require(`../template/${template}`).hasFacet(env, projectPath, options)
63
+ const cap = {
64
+ for: options?.for,
70
65
  pLanguage: DEFAULT_P_LANGUAGE,
71
66
  srv: {},
72
67
  db: [],
73
68
  requires: [],
74
- get isMultitenant() {
75
- return options?.add?.has('mtx') || options?.add?.has('multitenancy') ||
76
- env.requires.multitenancy || env.requires.db?.multiTenant ||
77
- this.isJava && fs.existsSync(path.join('mtx', 'sidecar')) ||
78
- false
79
- },
80
- get isExtensible() {
81
- return options?.add?.has('mtx') || options?.add?.has('extensibility') || env.requires.extensibility ||
82
- this.isJava && fs.existsSync('.cdsrc.json') && JSON.parse(fs.readFileSync('.cdsrc.json', 'utf8'))?.requires?.extensibility === true
83
- },
84
- get isNodejs() {
85
- return this.pLanguage === P_LANGUAGE_NODEJS
86
- },
87
- get isJava() {
88
- return this.pLanguage === P_LANGUAGE_JAVA
89
- },
90
- get hasRequires() {
91
- return this.requires.length > 0
92
- },
93
- get hasRequiresDb() {
94
- return this.db.length > 0
95
- },
96
- get hasHana() {
97
- return this.requires.some(({ resource }) => resource.isHana)
98
- },
99
- get hasXsuaa() {
100
- return this.requires.some(({ resource }) => resource.isXsuaa)
101
- },
102
- get hasAuditlog() {
103
- return false // TODO: Need a `cds.requires.auditlog` or some other way to detect if enabled.
104
- },
105
- get needsSidecar() {
106
- return (options?.add.has('hana') || this.db.length > 0) && (this.isJava || !this.isMultitenant)
107
- },
108
- get hasApprouter() {
109
- return !!env.requires.approuter
110
- },
111
- get hasMessaging() {
112
- return !!env.requires.messaging
113
- },
114
- get hasDestination() {
115
- const kinds = { odata: 1, 'odata-v2': 1, 'odata-v4': 1, rest: 1 }
116
- return Object.entries(env.requires).some(([service, require]) => !kinds[service] && kinds[require.kind])
117
- },
118
- get hasHtml5Repo() {
119
- return fs.existsSync(path.join(projectPath, 'chart', 'templates', 'html5-apps-repo-host.yaml'))
120
- },
121
- get hasMtaDeployment() {
122
- if (!options?.add.has('mta') && options?.add.has('helm')) return false
123
- return fs.existsSync(path.join(projectPath, 'mta.yaml'))
124
- },
125
- get hasHelmDeployment() {
126
- if (options?.add.has('mta') && !options?.add.has('helm')) return false
127
- return fs.existsSync(path.join(projectPath, 'chart', 'values.yaml'))
128
- }
69
+ get root() { return projectPath },
70
+ get hasRequires() { return this.requires.length > 0 },
71
+ get needsSidecar() { return (options?.add.has('hana') || this.db.length > 0) && (this.isJava || !this.isMultitenant) },
72
+ get isNodejs() { return this.pLanguage === P_LANGUAGE_NODEJS },
73
+ get isJava() { return this.pLanguage === P_LANGUAGE_JAVA },
74
+ get isMultitenant() { return _hasFacet('multitenancy') },
75
+ get isExtensible() { return _hasFacet('extensibility') },
76
+ get hasHana() { return _hasFacet('hana') },
77
+ get hasXsuaa() { return _hasFacet('xsuaa') },
78
+ get hasAuditlog() { return _hasFacet('auditlog') },
79
+ get hasApprouter() { return _hasFacet('approuter') },
80
+ get hasMessaging() { return _hasFacet('enterprise-messaging') },
81
+ get hasDestination() { return _hasFacet('destinations') },
82
+ get hasHtml5Repo() { return _hasFacet('html5-repo') },
83
+ get hasMta() { return _hasFacet('mta') },
84
+ get hasHelm() { return _hasFacet('helm') },
129
85
  }
130
86
 
131
87
  // setting the effective env as it might have changed by already executed 'add' commands, e.g. cds init foo --add hana,mta
@@ -133,23 +89,25 @@ module.exports = class ProjectReader {
133
89
  cds.env // make sure env is initialized, otherwise we cannot set it (exotic case)
134
90
  cds.env = env
135
91
 
136
- const buildTasks = await new BuildTaskFactory(this.logger.isDebug ? this.logger : nullLogger, cds).getTasks({ root: this.projectPath, resolve: true, mta: false })
92
+ const root = this.projectPath
93
+ const nullLogger = { debug: () => {}, log: () => {}, warn: () => {}, error: () => {}, write: () => {}}
94
+ const buildTasks = await new BuildTaskFactory(nullLogger, cds).getTasks({ root, resolve: true, mta: false })
137
95
 
138
96
  await Promise.all(buildTasks.map(async (task) => {
139
- const relDestPath = ProjectReader._slash(path.relative(this.projectPath, task.dest))
97
+ const relDestPath = path.relative(root, task.dest)?.replace(/\\/g, '/')
140
98
 
141
99
  switch (task.for) {
142
100
  case BUILD_TASK_HANA:
143
101
  // check whether we have a hana service binding defined as a hana build task is returned by default for compatibility reasons if neither hana, nor sqlite has been defined
144
102
  if (this._hasHanaServiceBinding(env)) {
145
- capDescriptor.db.push({ path: relDestPath, name: path.basename(task.src) })
103
+ cap.db.push({ path: relDestPath, name: path.basename(task.src) })
146
104
  }
147
105
  break
148
106
 
149
107
  case BUILD_TASK_JAVA:
150
108
  case BUILD_TASK_JAVA_CF:
151
- capDescriptor.pLanguage = P_LANGUAGE_JAVA
152
- capDescriptor.srv = {
109
+ cap.pLanguage = P_LANGUAGE_JAVA
110
+ cap.srv = {
153
111
  path: relDestPath,
154
112
  name: path.basename(task.src),
155
113
  ext: await this._getJavaExtDescriptor(task)
@@ -158,7 +116,7 @@ module.exports = class ProjectReader {
158
116
 
159
117
  case BUILD_TASK_NODEJS:
160
118
  case BUILD_TASK_NODE_CF:
161
- capDescriptor.srv = {
119
+ cap.srv = {
162
120
  path: relDestPath,
163
121
  name: path.basename(task.src)
164
122
  }
@@ -170,17 +128,17 @@ module.exports = class ProjectReader {
170
128
  break
171
129
 
172
130
  default:
173
- this.logger.warn(`Unknown build task type ${task.for}`)
131
+ LOG.warn(`Unknown build task type ${task.for}`)
174
132
  }
175
133
  }))
176
134
 
177
135
  // setting app name, app description...
178
- await this._setAppDetails(capDescriptor)
136
+ await this._setAppDetails(cap)
179
137
 
180
138
  // setting required resource information
181
- this._setResourceDependencies(env, capDescriptor)
139
+ this._setResourceDependencies(env, cap)
182
140
 
183
- return capDescriptor
141
+ return cap
184
142
  }
185
143
 
186
144
  /**
@@ -188,206 +146,127 @@ module.exports = class ProjectReader {
188
146
  * @param {object} task
189
147
  */
190
148
  async _getJavaExtDescriptor(task) {
191
- let descriptor = null
192
149
  // by default spring-boot:repackage is used creating an executable jar archive
193
150
  const pomJson = await this._parseXml(path.join(task.src, 'pom.xml'))
194
151
  if (pomJson) {
195
- // cannot use 'instanceof ManifestTemplate' since ManifestTemplate requires projectReader -> cycle created
196
- if (this.template.constructor.name === 'ManifestTemplate' && this._isOldJavaRuntime(pomJson)) {
152
+ const dependencies = pomJson?.project?.dependencies?.[0]?.dependency
153
+ const isOldJavaRuntime = [...dependencies].some(d => /com\.sap\.cloud\.servicesdk/.test(d?.groupId?.[0]))
154
+ if (isOldJavaRuntime) {
197
155
  throw new Error("Projects using the classic CAP Java SDK are not supported")
198
156
  }
199
157
 
200
- descriptor = {
201
- archiveName: path.basename(this.projectPath) + "-exec.jar"
202
- }
203
- const archiveName = ProjectReader._getProperty(pomJson, 'project.artifactId.0')
204
- if (archiveName) {
205
- if (ProjectReader._getProperty(pomJson, 'project.packaging.0') === 'war') {
206
- descriptor.archiveName = archiveName + '-exec.war'
207
- } else {
208
- descriptor.archiveName = archiveName + '-exec.jar'
209
- }
210
- }
158
+ const descriptor = { archiveName: path.basename(this.projectPath) + "-exec.jar" }
159
+ const archiveName = pomJson?.project?.artifactId?.[0]
160
+ const suffix = pomJson?.project?.packaging?.[0]
161
+ if (archiveName) descriptor.archiveName = archiveName + '-exec.' + suffix
162
+ return descriptor
211
163
  }
212
- return descriptor
213
164
  }
214
165
 
215
166
  async _getUiDescriptor(env) {
216
- const uiDescriptor = {
217
- appPath: null
218
- }
219
-
220
167
  const absoluteAppPath = path.join(this.projectPath, env.folders.app)
221
- if (await this.fsUtil.pathExists(absoluteAppPath)) {
222
- uiDescriptor.appPath = env.folders.app
168
+ return {
169
+ appPath: exists(absoluteAppPath) ? env.folders.app : null
223
170
  }
224
-
225
- return uiDescriptor
226
171
  }
227
172
 
228
- async _setAppDetails(capDescriptor) {
229
- if (capDescriptor.pLanguage !== P_LANGUAGE_JAVA && capDescriptor.pLanguage !== P_LANGUAGE_NODEJS) {
230
- throw Error(`${capDescriptor.pLanguage} not supported`)
173
+ async _setAppDetails(cap) {
174
+ if (cap.pLanguage !== P_LANGUAGE_JAVA && cap.pLanguage !== P_LANGUAGE_NODEJS) {
175
+ throw Error(`${cap.pLanguage} not supported`)
231
176
  }
232
177
 
233
178
  // 1.1. java - use parent pom.xml
234
- if (capDescriptor.pLanguage === P_LANGUAGE_JAVA) {
179
+ if (cap.pLanguage === P_LANGUAGE_JAVA) {
235
180
  const pomXmlPath = path.join(this.projectPath, 'pom.xml')
236
181
  try {
237
- if (await this.fsUtil.pathExists(pomXmlPath)) {
182
+ if (exists(pomXmlPath)) {
238
183
  const pomJson = await this._parseXml(pomXmlPath)
239
- const artifactId = ProjectReader._getProperty(pomJson, 'project.artifactId.0')
240
- const description = ProjectReader._getProperty(pomJson, 'project.description.0')
241
- let version = ProjectReader._getProperty(pomJson, 'project.version.0')
184
+ const [artifactId] = pomJson?.project?.artifactId ?? []
185
+ const [description] = pomJson?.project?.description ?? []
186
+ let [version] = pomJson?.project?.version ?? []
242
187
  // might need to resolve <version>${revision}</version> property
243
- const versionMatch = version.match(/\${1}\{{1}([\w-\\.]+)\}{1}/)
244
- if (versionMatch && versionMatch.length === 2) {
245
- const resolved = ProjectReader._getProperty(pomJson, 'project.properties.0.' + versionMatch[1] + '.0')
246
- if (resolved) {
247
- version = resolved
248
- }
188
+ if (version.match(/\${1}\{{1}([\w-\\.]+)\}{1}/)?.length === 2) {
189
+ version = pomJson?.project?.properties?.[0].revision[0]
249
190
  }
250
191
  if (version) {
251
192
  // check compliance with mta version format - copied from mbt build error message
252
193
  // if not compliant we use a static default value, see below...
253
194
  if (/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$/.test(version)) {
254
- capDescriptor.appVersion = version
195
+ cap.appVersion = version
255
196
  }
256
197
  }
257
198
  if (artifactId) {
258
- const nameMatch = artifactId.match(/([\w-\\.]+)(-parent)+/)
259
- if (Array.isArray(nameMatch)) {
260
- capDescriptor.appName = nameMatch.length === 3 ? nameMatch[1] : artifactId
261
- }
199
+ cap.appName = artifactId.split(/-parent/)[0]
262
200
  }
263
201
  if (description) {
264
- capDescriptor.appDescription = description
202
+ cap.appDescription = description
265
203
  }
266
204
  }
267
205
  } catch (e) {
268
- this.logger.error(`Failed to read file ${pomXmlPath} - skip pom.xml data`)
269
- this.logger.error(e)
206
+ LOG.error(`Failed to read file ${pomXmlPath} - skip pom.xml data`)
207
+ LOG.error(e)
270
208
  }
271
209
  }
272
210
 
273
211
  // 1.2. use package.json
274
- if (!capDescriptor.appName) {
212
+ if (!cap.appName) {
275
213
  const packageJsonPath = path.join(this.projectPath, 'package.json')
276
- if (await this.fsUtil.pathExists(packageJsonPath)) {
214
+ if (exists(packageJsonPath)) {
277
215
  try {
278
- const packageJson = await this.fsUtil.readJSON(packageJsonPath, 'utf-8')
279
- if (!capDescriptor.appDescription && packageJson.description) {
280
- capDescriptor.appDescription = packageJson.description
281
- }
282
- if (!capDescriptor.appVersion && packageJson.version) {
283
- capDescriptor.appVersion = packageJson.version
284
- }
285
- if (packageJson.name) {
286
- let segments = packageJson.name.trim().split('/')
287
- // scope as namespace
288
- if (segments[0].startsWith('@')) {
289
- segments[0] = segments[0].replace('@', '')
290
- }
291
- segments = segments.map(segment => segment.startsWith('@') ? encodeURIComponent(segment.replace('@', '')) : encodeURIComponent(segment))
292
- if (!capDescriptor.appName) {
293
- capDescriptor.appName = segments[segments.length - 1]
294
- }
295
- if (!capDescriptor.appId) {
296
- capDescriptor.appId = segments.join('.')
297
- }
298
- if (!capDescriptor.appDescription) {
299
- capDescriptor.appDescription = segments[segments.length - 1]
300
- }
216
+ const { name, version, description } = await read(packageJsonPath)
217
+ cap.appDescription = cap.appDescription ?? description
218
+ cap.appVersion = cap.appVersion ?? version
219
+ if (name) {
220
+ const segments = name.trim().replace(/@/g, '').split('/').map(encodeURIComponent)
221
+ cap.appName = cap.appName ?? segments[segments.length - 1]
222
+ cap.appId = cap.appId ?? segments.join('.')
223
+ cap.appDescription = cap.appDescription ?? cap.appName
301
224
  }
302
225
  } catch (e) {
303
- this.logger.error(`Failed to read file ${packageJsonPath} - skip application data`)
304
- this.logger.error(e)
226
+ LOG.error(`Failed to read file ${packageJsonPath} - skip application data`)
227
+ LOG.error(e)
305
228
  }
306
229
  }
307
230
  }
308
231
 
309
232
  // 2. use project name and static default values
310
- if (!capDescriptor.appName) {
311
- const projectName = path.basename(this.projectPath)
312
- capDescriptor.appName = projectName
313
- }
314
- if (!capDescriptor.appDescription) {
315
- capDescriptor.appDescription = capDescriptor.appName + " application"
316
- }
317
- if (!capDescriptor.appId) {
318
- capDescriptor.appId = capDescriptor.appName
319
- }
320
- if (!capDescriptor.appVersion) {
321
- capDescriptor.appVersion = '1.0.0'
322
- }
233
+ cap.appName = cap.appName ?? path.basename(this.projectPath)
234
+ cap.appDescription = cap.appDescription ?? cap.appName + " application"
235
+ cap.appId = cap.appId ?? cap.appName
236
+ cap.appVersion = cap.appVersion ?? '1.0.0'
323
237
  }
324
238
 
325
- _setResourceDependencies(env, capDescriptor) {
326
- const hanaResources = new Map()
327
- const otherResources = new Map()
328
-
329
- for (const key of Object.keys(env.requires)) {
330
- // ignore abstract helper definitions like sql
331
- if (key === 'sql' || key === 'sqlite' || key === 'hana') continue
332
- const { kind } = env.requires[key]
333
- if (kind === 'hana' || kind === 'hana-mt' || kind === 'hana-cloud') {
334
- hanaResources.set(key, env.requires[key])
335
- } else {
336
- otherResources.set(key, env.requires[key])
337
- }
338
- }
339
-
340
- for (const [key, value] of hanaResources) {
341
- const resource = this._getReqResource(key, value, capDescriptor)
342
- if (resource) {
343
- // using production profile
344
- capDescriptor.requires.push({ resource })
345
- }
346
- }
347
- // add after hana resource dependencies have been handled as multitenant information is required
348
- for (const [key, value] of otherResources) {
349
- const resource = this._getReqResource(key, value, capDescriptor)
350
- if (resource) {
351
- // using production profile
352
- capDescriptor.requires.push({ resource })
353
- }
239
+ _setResourceDependencies(env, cap) {
240
+ for (const [key, require] of Object.entries(env.requires)) {
241
+ if (key === 'sql' || key === 'sqlite' || key === 'hana') continue // ignore abstract helper definitions like sql
242
+ const resource = this._getReqResource(key, require, cap)
243
+ if (resource) cap.requires.push({ resource }) // using production profile
354
244
  }
355
245
  }
356
246
 
357
247
  _hasHanaServiceBinding(env) {
358
- const kind = ProjectReader._getProperty(env, 'requires.db.kind')
359
- return kind === 'hana' || kind === 'sql' && (
360
- ProjectReader._getProperty(env, 'requires.db.dialect') === 'hana' ||
361
- ProjectReader._getProperty(env, 'requires.db.use') === 'hana'
362
- )
248
+ const db = env?.requires?.db
249
+ return db?.kind === 'hana' || db?.kind === 'sql' && (db?.dialect === 'hana' || db?.use === 'hana')
363
250
  }
364
251
 
365
- _getReqResource(key, reqEntry, capDescriptor) {
252
+ _getReqResource(key, reqEntry, cap) {
253
+ const { kind, dialect, use, vcap = {}, multiTenant } = reqEntry
254
+ const { appName, isMultitenant } = cap
366
255
  const resource = {
367
- name: ProjectReader._getResourceName(key, capDescriptor),
256
+ name: !key.includes(appName) ? `${appName}-${key}` : key,
368
257
  service: '',
369
- get isMultitenant() {
370
- return capDescriptor.isMultitenant || reqEntry.multiTenant || false
371
- },
372
- vcap: reqEntry.vcap || {},
373
- get isManagedHana() {
374
- return this.service === 'service-manager' || this.vcap.label === 'service-manager'
375
- },
376
- get isPlainHana() {
377
- return this.service === 'hana' && !this.isManagedHana
378
- },
379
- get isHana() {
380
- return this.isPlainHana || this.isManagedHana
381
- },
382
- get isXsuaa() {
383
- return this.service === 'xsuaa'
384
- },
258
+ vcap,
259
+ get isMultitenant() { return isMultitenant || multiTenant || false },
260
+ get isManagedHana() { return this.vcap.label === 'service-manager' || this.service === 'service-manager' },
261
+ get isPlainHana() { return this.service === 'hana' && !this.isManagedHana },
262
+ get isHana() { return this.isPlainHana || this.isManagedHana },
263
+ get isXsuaa() { return this.service === 'xsuaa' },
385
264
  }
386
265
 
387
- switch (reqEntry.kind) {
266
+ switch (kind) {
388
267
  case 'sql':
389
268
  case 'hana':
390
- if (reqEntry.kind === 'sql' && reqEntry.dialect !== 'hana' && reqEntry.use !== 'hana') {
269
+ if (kind === 'sql' && dialect !== 'hana' && use !== 'hana') {
391
270
  break
392
271
  }
393
272
  // use different service name to avoid conflicts when switching from single tenant to multitenant
@@ -402,16 +281,11 @@ module.exports = class ProjectReader {
402
281
  break
403
282
 
404
283
  case 'xsuaa':
405
- resource.service = reqEntry.kind
284
+ resource.service = kind
406
285
  resource.vcap.plan = 'application'
407
286
  break
408
-
409
287
  default: {
410
- if (ProjectReader._getProperty(reqEntry, 'vcap.plan')) {
411
- resource.service = reqEntry.kind
412
- } else {
413
- this.logger.debug(`Skip resource ${key}`)
414
- }
288
+ vcap.plan ? resource.service = kind : DEBUG && DEBUG(`Skip resource ${key}`)
415
289
  }
416
290
  }
417
291
  return resource.service ? resource : null
@@ -429,7 +303,7 @@ module.exports = class ProjectReader {
429
303
  const xmljs = require('xml-js');
430
304
  let xmljsParse = {};
431
305
  try {
432
- const xml = await this.fsUtil.readFile(pomXmlPath, 'utf-8')
306
+ const xml = await read(pomXmlPath, 'utf-8')
433
307
  xmljsParse.project = JSON.parse(await xmljs.xml2json(xml, {
434
308
  ignoreComment: true,
435
309
  compact: true,
@@ -441,12 +315,12 @@ module.exports = class ProjectReader {
441
315
  ignoreInstruction: true,
442
316
  ignoreCdata: true,
443
317
  ignoreDoctype: true,
444
- textFn: this.removeTextAttributes
318
+ textFn: this._removeTextAttributes
445
319
  })).project[0];
446
320
  return xmljsParse;
447
321
  } catch (e) {
448
- this.logger.error(`Failed to read file ${pomXmlPath} - skip pom.xml data`)
449
- this.logger.error(e)
322
+ LOG.error(`Failed to read file ${pomXmlPath} - skip pom.xml data`)
323
+ LOG.error(e)
450
324
  }
451
325
  }
452
326
 
@@ -455,63 +329,14 @@ module.exports = class ProjectReader {
455
329
  input : "groupId":[{"_text":["customer"]}]
456
330
  output: "groupId":["customer"]
457
331
  */
458
- removeTextAttributes(value, parentElement) {
459
- const parentElementsObj = Object.keys(parentElement._parent);
460
- const key = parentElementsObj.length;
461
- const keyName = parentElementsObj[key - 1];
462
- const arrKey = parentElement._parent[keyName];
463
- const arrKeyLen = arrKey.length;
464
- if (arrKeyLen > 0) {
465
- const arr = arrKey;
466
- const arrIndex = arrKey.length - 1;
467
- arr[arrIndex] = value;
468
- } else {
469
- parentElement._parent[keyName] = value;
470
- }
471
- }
472
- async _validate(projectDescriptor) {
473
- const capDescriptor = projectDescriptor.cap
474
- if (capDescriptor.db.length > 0) {
475
- if (capDescriptor.isJava) {
476
- //ensure package.json exists in db module
477
- await Promise.all(capDescriptor.db.map(async db => {
478
- const dbPath = path.join(this.projectPath, db.path, "package.json")
479
- if (!await this.fsUtil.pathExists(dbPath)) {
480
- this.logger.error(`Missing package.json file in folder ${db.path} - 'cds add hana' will add the configuration for SAP HANA deployment`) //NOSONAR
481
- }
482
- }))
483
- }
332
+ _removeTextAttributes(value, parentElement) {
333
+ const parentKeys = Object.keys(parentElement._parent);
334
+ const key = parentKeys[parentKeys.length - 1];
335
+ const _text = parentElement._parent[key];
336
+ if (_text.length > 0) {
337
+ _text[_text.length - 1] = value;
484
338
  } else {
485
- //this.logger.warn(`No SAP HANA service binding defined in the requires section of the project's package.json. Database module omitted`) //NOSONAR
486
- }
487
- }
488
-
489
- _isOldJavaRuntime(pomJson) {
490
- const dependencies = ProjectReader._getProperty(pomJson, "project.dependencies.0.dependency")
491
- if (Array.isArray(dependencies)) {
492
- return dependencies.some((dependency) => {
493
- const groupId = ProjectReader._getProperty(dependency, 'groupId.0')
494
- return /com\.sap\.cloud\.servicesdk/.test(groupId)
495
- })
339
+ parentElement._parent[key] = value;
496
340
  }
497
- return false
498
- }
499
-
500
- static _slash(filename) {
501
- return filename ? filename.replace(/\\/g, '/') : filename
502
- }
503
-
504
- static _getResourceName(key, capDescriptor) {
505
- let name = key
506
- // only if not already present
507
- if (!name.includes(capDescriptor.appName)) {
508
- name = capDescriptor.appName + '-' + name
509
- }
510
- return name
511
- }
512
-
513
- static _getProperty(src, segments) {
514
- segments = Array.isArray(segments) ? segments : segments.split('.')
515
- return segments.reduce((p, n) => p && p[n], src)
516
341
  }
517
342
  }
@@ -16,9 +16,11 @@ const ERASE_LINE = ESC_SEQ + 'K';
16
16
  // const SAVE_CURSOR = ESC_SEQ + 's';
17
17
  // const RESTORE_CURSOR = ESC_SEQ + 'u';
18
18
 
19
+ const LOG = console
20
+ const DEBUG = /\bcli\b/.test(process.env.DEBUG) && LOG.debug
21
+
19
22
  class Spinner {
20
- constructor(logger, theme = Spinner.THEME_SPIN, initialDelay = 1000) {
21
- this.logger = logger;
23
+ constructor(theme = Spinner.THEME_SPIN, initialDelay = 1000) {
22
24
  this.theme = theme;
23
25
  this.initialDelay = initialDelay;
24
26
  }
@@ -40,22 +42,22 @@ class Spinner {
40
42
  }
41
43
 
42
44
  _start(msg) {
43
- if (!this.logger.isDebug && this.logger.isTTY) {
44
- this.logger.log(msg + HIDE_CURSOR + ERASE_LINE + MOVE_CURSOR_UP);
45
+ if (!DEBUG && process.stdout.isTTY) {
46
+ LOG.info(msg + HIDE_CURSOR + ERASE_LINE + MOVE_CURSOR_UP);
45
47
  this.timeoutId = setTimeout(() => {
46
48
  let idx = 0;
47
49
  this.intervalId = setInterval(() => {
48
- this.logger.log(msg + ' ' + SPINNER_THEMES[this.theme][idx] + ERASE_LINE + MOVE_CURSOR_UP);
50
+ LOG.info(msg + ' ' + SPINNER_THEMES[this.theme][idx] + ERASE_LINE + MOVE_CURSOR_UP);
49
51
  idx = (idx + 1) % SPINNER_THEMES[this.theme].length;
50
52
  }, RENDER_DELAY);
51
53
  }, this.initialDelay);
52
54
  } else {
53
- this.logger.log(msg);
55
+ LOG.info(msg);
54
56
  }
55
57
  }
56
58
 
57
59
  _stop(msg, postfix) {
58
- if (!this.logger.isDebug && this.logger.isTTY) {
60
+ if (!DEBUG && process.stdout.isTTY) {
59
61
  if (this.timeoutId) {
60
62
  clearTimeout(this.timeoutId);
61
63
  this.timeoutId = null;
@@ -66,7 +68,7 @@ class Spinner {
66
68
  this.intervalId = null;
67
69
  }
68
70
 
69
- this.logger.log(SHOW_CURSOR + msg + ' ' + postfix + ERASE_LINE);
71
+ LOG.info(SHOW_CURSOR + msg + ' ' + postfix + ERASE_LINE);
70
72
  }
71
73
  }
72
74
  }