@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,11 +1,15 @@
1
+ const cds = require('../../../cds')
2
+ const { fs: { promises: { rename } }, path: { basename, dirname, join }, exists } = cds.utils
1
3
  const { readProject } = require('../../projectReader')
4
+ const { renderAndCopy } = require('../../template')
2
5
  const mvn = require('../../mvn')
3
-
6
+ const { dim } = require('../../../util/term')
7
+ const { filterStringAsRegex } = require('../../add')
4
8
 
5
9
  module.exports = class Handler extends require('../../plugin') {
6
10
 
7
11
  static help() {
8
- return 'add handler stubs for actions and functions (Java)'
12
+ return 'handler stubs for service entities, actions and functions'
9
13
  }
10
14
 
11
15
  options() {
@@ -13,25 +17,96 @@ module.exports = class Handler extends require('../../plugin') {
13
17
  'filter': {
14
18
  type: 'string',
15
19
  short: 'f',
16
- help: `Filter actions or functions by name prefix. Regular expressions are not supported.`
20
+ help: `Filter for entities, actions or functions matching the given pattern.
21
+ For Node.js, if it contains meta characters like '^' or '*', it is treated as a regular expression,
22
+ otherwise as an include pattern, i.e /.*bookshop.*/i
23
+ For Java, only '*' and '**' as suffix wildcards are allowed, as in 'my.bookshop.*' or 'my.**'`
17
24
  },
18
25
  'out': {
19
26
  type: 'string',
20
27
  short: 'o',
21
- help: 'Specify a custom output directory inside the `handlers` directory. The default is `handlers`.'
28
+ help: `Custom output directory.
29
+ For Java, the default is 'handlers'. For Node.js, the default is 'srv'.`
22
30
  }
23
31
  }
24
32
  }
25
33
 
26
34
  canRun() {
27
- const { isJava } = readProject()
28
- if (!isJava) {
29
- throw `No Java project detected. 'cds add handler' is currently only supported for Java projects.\n`
30
- }
31
35
  return true
32
36
  }
33
37
 
34
38
  async run() {
35
- await mvn.generate('handler')
39
+ const { isJava } = readProject()
40
+ if (isJava) {
41
+ await mvn.generate('handler')
42
+ } else {
43
+ await createNodeHandler()
44
+ }
45
+ }
46
+ }
47
+
48
+ async function createNodeHandler() {
49
+ const { filter, force, out=cds.env.folders.srv } = cds.cli.options
50
+ const proj = readProject()
51
+ const csn = await loadModel()
52
+ if (!csn) return console.log(dim(`> Skipping, no model found`))
53
+
54
+ const nameFilter = filterStringAsRegex(filter)
55
+
56
+ for (const service of csn.services) {
57
+ if (!service.$location.file) continue
58
+
59
+ const destFileType = proj.isTypescript ? '.ts' : '.js'
60
+ const outPath = join(out, basename(service.$location.file.replace('.cds', destFileType)))
61
+ const destFile = join(cds.root, outPath)
62
+ if (!force && exists(destFile)) {
63
+ console.log(dim(`> Skipping ${outPath}`))
64
+ continue
65
+ }
66
+
67
+ proj.serviceName = service.name
68
+ proj.serviceClass = service.name.split('.').pop()
69
+ proj.servicePath = proj.serviceName ? (proj.serviceName.replace(/\./g, '/')) : ''
70
+
71
+ proj.entities = Object.entries(service.entities)
72
+ .filter(([name]) => name.match(nameFilter))
73
+ .filter(([name]) => !name.match(/[._]texts$/))
74
+ .filter(([, e]) => !e['@cds.autoexposed']) // only consider non-autoexposed entities to reduce clutter
75
+ .map(([name]) => ({
76
+ name,
77
+ dataVar: name.charAt(0).toLowerCase() + name.slice(1) // lower case first letter
78
+ }))
79
+ if (proj.entities.length)
80
+ proj.entityNames = proj.entities.map(e => e.name).join(', ')
81
+
82
+ proj.operations = Object.keys(service.actions)
83
+ .filter(name => name.match(nameFilter))
84
+ .map(name => ({
85
+ name,
86
+ dataVar: name.charAt(0).toLowerCase() + name.slice(1) // lower case first letter
87
+ }))
88
+
89
+ const all = [...proj.entities, ...proj.operations]
90
+ if (all.length > 4)
91
+ proj.allNames = `\n ${all.map(e => e.name).join(',\n ')}\n`
92
+ else if (all.length)
93
+ proj.allNames = all.map(e => e.name).join(', ')
94
+
95
+ console.log(dim(`> Writing ${outPath}`))
96
+ const destPath = dirname(destFile)
97
+ await renderAndCopy(join(__dirname, 'files'), destPath, proj)
98
+ await rename(join(destPath, 'handler.xs'), destFile)
99
+ }
100
+ }
101
+
102
+ /**
103
+ * @returns { Promise<import('@cap-js/cds-types').linked.LinkedCSN | null> }
104
+ */
105
+ async function loadModel() {
106
+ try {
107
+ return cds.linked(cds.minify(await cds.load([cds.env.roots])))
108
+ } catch (err) {
109
+ if (err.code === 'MODEL_NOT_FOUND') return null
110
+ throw err
36
111
  }
37
112
  }
@@ -95,7 +95,7 @@ module.exports = class HelmTemplate extends require('../../plugin') {
95
95
  }
96
96
 
97
97
  async combine() {
98
- const { hasDestination, hasEnterpriseMessaging, hasEnterpriseMessagingShared, hasHana, hasMultitenancy, hasXsuaa, hasHtml5Repo, hasApprouter, hasKafka, hasAttachments, hasPortal, hasMalwareScanner, hasDynatrace } = readProject()
98
+ const { hasDestination, hasEnterpriseMessaging, hasEnterpriseMessagingShared, hasHana, hasMultitenancy, hasXsuaa, hasHtml5Repo, hasApprouter, hasKafka, hasAttachments, hasPortal, hasMalwareScanner, hasDynatrace, hasCloudLogging } = readProject()
99
99
 
100
100
  if (hasApprouter) {
101
101
  await this._mergeDependency('web-application', 'approuter')
@@ -142,6 +142,9 @@ module.exports = class HelmTemplate extends require('../../plugin') {
142
142
  if (hasMalwareScanner) {
143
143
  await this._mergeDependency('service-instance', 'malware-scanner')
144
144
  }
145
+ if (hasCloudLogging) {
146
+ await this._mergeDependency('service-instance', 'cloud-logging')
147
+ }
145
148
  if (hasDynatrace) {
146
149
  await this._mergeDependency('service-instance', 'dynatrace')
147
150
  }
@@ -1,4 +1,5 @@
1
1
  {
2
+ {{#hasIndexHtml}}"welcomeFile": "/index.html",{{/hasIndexHtml}}
2
3
  "authenticationMethod": "route",
3
4
  "routes": [
4
5
  {
@@ -3,6 +3,7 @@ const { readProject } = require('../../projectReader')
3
3
  const { merge } = require('../../merge')
4
4
  const { srv4, html5RepoHost, html5Runtime, appDeployer, approuter, mtxSidecar4 } = require('../../registries/mta')
5
5
  const { odataApi } = require('../../registries/xs-app')
6
+ const { exists } = require('../../../cds').utils
6
7
 
7
8
  module.exports = class Html5RepoTemplate extends require('../../plugin') {
8
9
 
@@ -53,6 +54,7 @@ module.exports = class Html5RepoTemplate extends require('../../plugin') {
53
54
 
54
55
  await Promise.all(apps.map(async ({app}) => {
55
56
  project.app = app
57
+ project.hasIndexHtml = exists(join(appPath, app, 'webapp/index.html'))
56
58
  const additions = [odataApi]
57
59
  await merge(__dirname, 'files/xs-app.json.hbs').into(join(appPath, app, 'xs-app.json'), { with: project, additions })
58
60
  }))
@@ -2,16 +2,21 @@ const https = require('node:https')
2
2
  const axios = require('axios')
3
3
  const asJson = require("../data/as-json")
4
4
  const cds = require('../../../cds')
5
- const cf = require("../../../util/cf")
5
+ const cf = require('../../../util/cf')
6
6
  const { write, exists } = cds.utils
7
- const { filterStringAsRegex } = require("../data/index")
7
+ const { filterStringAsRegex } = require('../../add')
8
+ const { _readSpringBootConfig } = require('../../projectReader')
9
+
10
+ /**
11
+ * @typedef {{name: string, entities:[], actions:[], urlPaths: string[], isJava:boolean}} ServiceData
12
+ * @typedef {Map<string, ServiceData>|undefined} ServiceMap
13
+ */
8
14
 
9
15
  module.exports = class HttpTemplate extends require('../../plugin') {
10
16
 
11
17
  #nodeHost = "http://localhost:4004"
12
18
  #javaHost = "http://localhost:8080"
13
19
  #defaultOutputDir = "test/http"
14
- #defaultAuth = `Authorization: Basic alice:`
15
20
 
16
21
  static help() {
17
22
  return 'add .http files for modeled services'
@@ -50,42 +55,16 @@ module.exports = class HttpTemplate extends require('../../plugin') {
50
55
  cds.linked(csn)
51
56
 
52
57
  const serviceInfo = cds.compile.to.serviceinfo(csn)
53
- let [hostname, auth] = await this.#getHostnameAndAuth(serviceInfo)
54
58
  let services = this.#collectServiceInfo(csn, serviceInfo)
55
59
  if (!services) return
56
60
 
57
61
  const payload = await this.#generatePayload(csn, services)
58
- this.#generateRequests(services, hostname, auth, payload)
59
- }
60
-
61
- async #getHostnameAndAuth(serviceInfo) {
62
- if (!cds.cli.options['for-app']) {
63
- if (serviceInfo[0].runtime === 'Java') {
64
- return [this.#javaHost, null]
65
- } else {
66
- return [this.#nodeHost, this.#defaultAuth]
67
- }
68
- }
69
-
70
- const app = cds.cli.options['for-app']
71
- // Later: differentiate between CF and Kyma
72
- const env = await cf.getEnv(app)
73
- if (!cds.cli.options.dry) console.error('Fetching credentials from Cloud Foundry app \'' + app + '\'...')
74
- const hostname = `https://${env.application_env_json.VCAP_APPLICATION.application_uris[0]}`
75
- const xsuaaCredentials = env.system_env_json.VCAP_SERVICES?.xsuaa[0]?.credentials ?? {}
76
- const { url, clientid, clientsecret, certurl, certificate, key } = xsuaaCredentials
77
- if (!url) throw new Error('No XSUAA service found in the environment of ' + app)
78
-
79
- const auth = certificate ? { maxRedirects: 0, httpsAgent: new https.Agent({ cert: certificate, key }) }
80
- : { auth: { username: clientid, password: clientsecret } }
81
- const authUrl = `${certurl ?? url}/oauth/token`
82
- const data = `grant_type=client_credentials&client_id=${encodeURI(clientid)}`
83
- const config = { method: 'POST', timeout: 5000, data, ...auth }
84
- const { access_token } = (await axios(authUrl, config)).data
85
- const approuterAuth = `x-approuter-authorization: bearer ${access_token}`
86
- return [hostname, approuterAuth]
62
+ await this.#generateRequests(services, payload, csn)
87
63
  }
88
64
 
65
+ /**
66
+ * @returns {ServiceMap}
67
+ */
89
68
  #collectServiceInfo(csn, serviceInfo) {
90
69
  const services = new Map()
91
70
 
@@ -105,7 +84,7 @@ module.exports = class HttpTemplate extends require('../../plugin') {
105
84
  }
106
85
  }
107
86
  if (entities.length === 0 && actions.length === 0) return
108
- services.set(s.name, { entities, urlPaths, actions })
87
+ services.set(s.name, { name: s.name, entities, urlPaths, actions, isJava: sInfo.runtime === 'Java' })
109
88
  })
110
89
 
111
90
  if (services.size === 0) {
@@ -126,6 +105,9 @@ module.exports = class HttpTemplate extends require('../../plugin') {
126
105
  }
127
106
  }
128
107
 
108
+ /**
109
+ * @param {ServiceMap} services
110
+ */
129
111
  async #generatePayload(csn, services) {
130
112
  const payload = {}
131
113
  for (const [, service] of services) {
@@ -145,9 +127,13 @@ module.exports = class HttpTemplate extends require('../../plugin') {
145
127
  return payload
146
128
  }
147
129
 
148
- #generateRequests(services, hostname, auth, payload) {
130
+ /**
131
+ * @param {ServiceMap} services
132
+ */
133
+ async #generateRequests(services, payload, csn) {
149
134
  let varsDefined = false // when writing requests for multiple services to stdout, define vars only once
150
135
  for (const [serviceName, service] of services) {
136
+ let [hostname, auth] = await this.#getHostnameAndAuth(service, csn)
151
137
  let requests = []
152
138
  if (!cds.cli.options.dry || !varsDefined) {
153
139
  requests.push(auth ? `@server = ${hostname}\n@auth = ${auth}\n` : `@server = ${hostname}\n`)
@@ -226,6 +212,87 @@ module.exports = class HttpTemplate extends require('../../plugin') {
226
212
  }
227
213
  }
228
214
 
215
+ async #getHostnameAndAuth(service, csn) {
216
+ if (!cds.cli.options['for-app']) {
217
+ const host = service.isJava ? this.#javaHost : this.#nodeHost
218
+ const users = Object.values(await this.#mockUsersForService(service, csn))
219
+ const authStr = users.length ? `Authorization: Basic ${users[0].name}:${users[0].password||''}` : null
220
+ return [host, authStr]
221
+ }
222
+
223
+ if (!this.approuterAuth) {
224
+ const app = cds.cli.options['for-app']
225
+ // Later: differentiate between CF and Kyma
226
+ const env = await cf.getEnv(app)
227
+ if (!cds.cli.options.dry) console.error('Fetching credentials from Cloud Foundry app \'' + app + '\'...')
228
+ this.hostname = `https://${env.application_env_json.VCAP_APPLICATION.application_uris[0]}`
229
+ const xsuaaCredentials = env.system_env_json.VCAP_SERVICES?.xsuaa[0]?.credentials ?? {}
230
+ const { url, clientid, clientsecret, certurl, certificate, key } = xsuaaCredentials
231
+ if (!url) throw new Error('No XSUAA service found in the environment of ' + app)
232
+
233
+ const auth = certificate ? { maxRedirects: 0, httpsAgent: new https.Agent({ cert: certificate, key }) }
234
+ : { auth: { username: clientid, password: clientsecret } }
235
+ const authUrl = `${certurl ?? url}/oauth/token`
236
+ const data = `grant_type=client_credentials&client_id=${encodeURI(clientid)}`
237
+ const config = { method: 'POST', timeout: 5000, data, ...auth }
238
+ const { access_token } = (await axios(authUrl, config)).data
239
+ this.approuterAuth = `x-approuter-authorization: bearer ${access_token}`
240
+ }
241
+ return [this.hostname, this.approuterAuth]
242
+ }
243
+
244
+ /**
245
+ * Reads mock users from the cds.env or the application.yml,
246
+ * returning only those that match to the declared roles of the given service.
247
+ * @param {ServiceData} service
248
+ * @returns {Promise<Record<string, {password: string, roles: string[]}>>} a map of users
249
+ */
250
+ async #mockUsersForService(service, csn) {
251
+ const declaredRoles = new Set()
252
+ Object.values(csn.definitions)
253
+ .filter(d => d.name.startsWith(service.name))
254
+ .map(d => d['@requires']).filter(Boolean)
255
+ .forEach(req => {
256
+ declaredRoles.add(...(Array.isArray(req) ? req : [req]))
257
+ })
258
+
259
+ const matchingUsers = {}
260
+ const addUserIfMatching = (roles, name, password) => {
261
+ if (!declaredRoles.size || declaredRoles.has('authenticated-user') || roles?.some(r => declaredRoles.has(r))) {
262
+ matchingUsers[name] = { name, password, roles }
263
+ }
264
+ }
265
+
266
+ if (service.isJava) {
267
+ const javaDefaultUsers = [
268
+ { name: 'authenticated', roles: ['authenticated-user'] },
269
+ { name: 'internal', roles: ['internal-user'] },
270
+ { name: 'system', roles: ['system-user'] },
271
+ ]
272
+ const javaUsers = []
273
+ this.javaConfig ??= await _readSpringBootConfig()
274
+ javaUsers.push(
275
+ ...this.javaConfig
276
+ .map(cfg => cfg.cds?.security?.mock?.users) // {name: { password, roles }}
277
+ .filter(Boolean)
278
+ .flatMap(usrList => Object.entries(usrList).map(([name, {password, roles}]) => ({ name, password, roles: roles??[] })))
279
+ , ...javaDefaultUsers // add default users at end for lower priority
280
+ )
281
+ javaUsers.forEach(({name, password='', roles}) => addUserIfMatching(roles, name, password))
282
+ }
283
+ // Node
284
+ else if (cds.env.requires.auth?.users) {
285
+ const nodeUsers = Object.entries(cds.env.requires.auth.users)
286
+ nodeUsers.forEach(([name, {password, roles}]) => addUserIfMatching(roles, name, password))
287
+ }
288
+ // add a dummy user as a hint if no user matches the scopes
289
+ if (declaredRoles.size && !matchingUsers.length) {
290
+ const name = `no_user_found_for_roles_${Array.from(declaredRoles).join('_')}`
291
+ matchingUsers[name] = { name: name, password: '', roles: [] }
292
+ }
293
+ return matchingUsers
294
+ }
295
+
229
296
  #write(requests, serviceName) {
230
297
  const http = requests.join('\n')
231
298
  if (cds.cli.options.dry) {
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "devDependencies": {
13
13
  "@cap-js/sqlite": "^1",
14
- "@cap-js/cds-types": "^0.7.0"
14
+ "@cap-js/cds-types": "^0.8.0"
15
15
  },
16
16
  "scripts": {
17
17
  "start": "cds-serve"
@@ -1,6 +1,13 @@
1
+ {{#isESM}}
2
+ import cds from '@sap/cds'
3
+
4
+ export class AdminService extends cds.ApplicationService { init() {
5
+ {{/isESM}}
6
+ {{^isESM}}
1
7
  const cds = require('@sap/cds')
2
8
 
3
- module.exports = class AdminService extends cds.ApplicationService { init(){
9
+ module.exports = class AdminService extends cds.ApplicationService { init() {
10
+ {{/isESM}}
4
11
 
5
12
  const { Books } = this.entities
6
13
 
@@ -1,6 +1,13 @@
1
+ {{#isESM}}
2
+ import cds from '@sap/cds'
3
+
4
+ export class CatalogService extends cds.ApplicationService { init() {
5
+ {{/isESM}}
6
+ {{^isESM}}
1
7
  const cds = require('@sap/cds')
2
8
 
3
- class CatalogService extends cds.ApplicationService { init() {
9
+ module.exports = class CatalogService extends cds.ApplicationService { init() {
10
+ {{/isESM}}
4
11
 
5
12
  const { Books } = cds.entities('sap.capire.bookshop')
6
13
  const { ListOfBooks } = this.entities
@@ -34,5 +41,3 @@ class CatalogService extends cds.ApplicationService { init() {
34
41
  // Delegate requests to the underlying generic service
35
42
  return super.init()
36
43
  }}
37
-
38
- module.exports = CatalogService
@@ -2,6 +2,7 @@ const { join } = require('path')
2
2
  const cds = require('../../../cds'), { exists, read, write, copy } = cds.utils
3
3
  const { env4, readProject } = require('../../projectReader')
4
4
  const { merge } = require('../../merge')
5
+ const { renderAndCopy } = require('../../template')
5
6
  const mvn = require('../../mvn')
6
7
 
7
8
  module.exports = class SampleTemplate extends require('../../plugin') {
@@ -13,19 +14,20 @@ module.exports = class SampleTemplate extends require('../../plugin') {
13
14
  async run() {
14
15
  const { db, srv, app } = env4('production').folders
15
16
  const language = exists('pom.xml') ? 'java' : 'nodejs'
17
+ const project = readProject()
16
18
 
17
19
  if (language === 'nodejs') {
18
20
  await Promise.all([
19
21
  copy(join(__dirname, 'files/db')).to(db),
20
22
  copy(join(__dirname, 'files/srv')).to(srv)
21
23
  ])
22
- await copy(join(__dirname, 'files', language)).to(srv)
24
+ await renderAndCopy(join(__dirname, 'files', language), srv, project)
23
25
  await copy(join(__dirname, 'files/app')).to(app)
24
26
  } else if (language === 'java') {
25
27
  await mvn.add('sample')
26
28
  }
27
29
 
28
- const { appName, appPath, appUIPaths } = readProject()
30
+ const { appName, appPath, appUIPaths } = project
29
31
  await merge(__dirname, 'files/package.json.hbs').into('package.json', { with: { appPath }})
30
32
 
31
33
  // manifest.json must be unique
@@ -0,0 +1,18 @@
1
+ {
2
+ "dependencies": {
3
+ "@cap-js/telemetry": ">=0",
4
+ "@grpc/grpc-js": "^1",
5
+ "@opentelemetry/exporter-trace-otlp-grpc": "^0",
6
+ "@opentelemetry/exporter-metrics-otlp-grpc": "^0",
7
+ "@opentelemetry/host-metrics": "^0"
8
+ },
9
+ "cds": {
10
+ "requires": {
11
+ "[{{profile}}]": {
12
+ "telemetry": {
13
+ "kind": "to-cloud-logging"
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -1,5 +1,6 @@
1
1
  const cds = require('../../../cds')
2
2
  const { readProject } = require('../../projectReader')
3
+ const { merge, sort } = require('../../merge')
3
4
 
4
5
  module.exports = class extends require('../../plugin') {
5
6
 
@@ -8,12 +9,17 @@ module.exports = class extends require('../../plugin') {
8
9
  }
9
10
 
10
11
  async canRun() {
11
- const { hasMta, hasHelm, hasHelmUnifiedRuntime } = readProject()
12
- if ((hasHelmUnifiedRuntime || hasHelm) && !hasMta) throw `'cds add telemetry' is not available for Kyma yet`
13
12
  return true
14
13
  }
15
14
 
16
15
  requires() {
17
16
  return ['cloud-logging']
18
17
  }
18
+
19
+ async run() {
20
+ const project = readProject();
21
+ project.profile ??= 'production'
22
+ await merge(__dirname, 'files/package.json.hbs').into('package.json', { with: project })
23
+ await sort('package.json', 'dependencies')
24
+ }
19
25
  }
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "devDependencies": {
3
- "@cap-js/cds-types": "^0.7.0",
3
+ "@cap-js/cds-types": "^0.8.0",
4
4
  "@types/node": "^22.0.0",
5
5
  "typescript": "^5"
6
6
  },
7
7
  "scripts": {
8
- "watch": "cds-ts w"
8
+ "watch": "cds-tsx w"
9
9
  }
10
10
  }
@@ -16,7 +16,7 @@ module.exports = class extends require('../../plugin') {
16
16
  }
17
17
 
18
18
  static hasInProduction(env) {
19
- return !!env.requires?.workzone
19
+ return !!env.requires?.workzone || cds.cli.options.add.has('workzone-standard')
20
20
  }
21
21
 
22
22
  canRun() {
@@ -16,12 +16,6 @@ export interface CDSGenerator {
16
16
  */
17
17
  stepEnd(): Promise<void>;
18
18
 
19
- /**
20
- * Contains the parameter description
21
- * DEPRECATED: use uiConfig
22
- */
23
- config?: any;
24
-
25
19
  /**
26
20
  * Contains the parameter description
27
21
  */
@@ -3,6 +3,7 @@ const LOG = cds.log('cli|watch'), DEBUG = cds.debug('cli|watch')
3
3
  const { relative } = require('path')
4
4
 
5
5
  module.exports = ({ cwd, script, ext, ignore=RegExp(), env={}, delay=200, options={} })=>{
6
+ const debugOpts = _debugOpts(options)
6
7
 
7
8
  let child
8
9
 
@@ -20,18 +21,16 @@ module.exports = ({ cwd, script, ext, ignore=RegExp(), env={}, delay=200, option
20
21
 
21
22
  // Re-starting child process...
22
23
  const { fork } = require('child_process')
23
- const restart = _coalesceEvents(delay, (events) => {
24
- DEBUG?.('Restart', {events})
24
+ const restart = _coalesceEvents(delay, (eventsAndOpts) => {
25
+ DEBUG?.('Restart', {events: eventsAndOpts})
25
26
  _kill (child, () => {
26
- const execArgv = [...process.execArgv]
27
- events.forEach(evt => {
28
- if (evt.type && evt.type.startsWith('--')) execArgv.push(evt.type)
29
- if (evt.name && evt.name.startsWith('--')) execArgv.push(evt.name)
27
+ const execArgv = [...process.execArgv, ...debugOpts]
28
+ eventsAndOpts.forEach(evt => { // ad-hoc options from stdin
29
+ if (evt.type?.startsWith('--')) execArgv.push(evt.type)
30
+ if (evt.name?.startsWith('--')) execArgv.push(evt.name)
30
31
  })
31
- if (options.debug) execArgv.push('--inspect')
32
- if (options['debug-brk']) execArgv.push('--inspect-brk')
33
32
 
34
- const updated = events.filter(evt => evt.type && evt.type === 'update').map(evt => evt.name)
33
+ const updated = eventsAndOpts.filter(evt => evt.type && evt.type === 'update').map(evt => evt.name)
35
34
  emitter.emit ('restart', updated)
36
35
  child = fork (script, { cwd, env, stdio:'inherit', execArgv }, (err)=>{
37
36
  if (err) console.error (err)
@@ -147,4 +146,21 @@ function _kill (child, cb=()=>{}) {
147
146
  child.kill()
148
147
  }
149
148
 
149
+ function _debugOpts(options={}) {
150
+ const o = []
151
+ if ('debug' in options) {
152
+ options.inspect = options.debug
153
+ delete options.debug
154
+ }
155
+ if ('inspect' in options) { // could be 0
156
+ if (typeof options.inspect === 'string') o.push(`--inspect=${options.inspect}`) // could be port and/or host
157
+ else o.push(`--inspect`)
158
+ }
159
+ if ('inspect-brk' in options) {
160
+ if (typeof options['inspect-brk'] === 'string') o.push(`--inspect-brk=${options['inspect-brk']}`)
161
+ else o.push(`--inspect-brk`)
162
+ }
163
+ return o
164
+ }
165
+
150
166
  const _pending = Symbol.for('sap.cds.watch.pendingKill')