@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.
- package/CHANGELOG.md +41 -3
- package/bin/cds.js +49 -28
- package/bin/debug.js +90 -0
- package/bin/repl.js +50 -4
- package/bin/watch.js +13 -3
- package/lib/bind/userCdsrcJsonStorageProvider.js +0 -19
- package/lib/build/buildTaskEngine.js +4 -4
- package/lib/build/provider/hana/2migration.js +34 -20
- package/lib/build/provider/hana/index.js +74 -41
- package/lib/build/provider/internalBuildTaskProvider.js +1 -1
- package/lib/build/provider/mtx-extension/index.js +4 -4
- package/lib/build/provider/mtx-sidecar/index.js +2 -2
- package/lib/build/provider/nodejs/index.js +3 -19
- package/lib/build/util.js +41 -17
- package/lib/cds.js +10 -2
- package/lib/deploy/to-hana/hdiDeployUtil.js +14 -10
- package/lib/import/message.js +2 -1
- package/lib/import/odata/metadataconverter/MetadataConverter.js +5 -0
- package/lib/import/odata/v4parser.js +32 -43
- package/lib/init/add.js +26 -0
- package/lib/init/constants.js +5 -6
- package/lib/init/index.js +17 -25
- package/lib/init/merge.js +2 -5
- package/lib/init/projectReader.js +76 -9
- package/lib/init/registries/mta.js +0 -1
- package/lib/init/template/approuter/files/package.json +1 -1
- package/lib/init/template/cloud-logging/files/package.json.hbs +1 -12
- package/lib/init/template/cloud-logging/files/values.yaml.hbs +16 -0
- package/lib/init/template/cloud-logging/index.js +14 -10
- package/lib/init/template/data/as-json.js +1 -1
- package/lib/init/template/data/index.js +1 -26
- package/lib/init/template/esm/files/package.json.hbs +3 -0
- package/lib/init/template/esm/index.js +16 -0
- package/lib/init/template/handler/files/handler.xs.hbs +56 -0
- package/lib/init/template/handler/index.js +84 -9
- package/lib/init/template/helm/index.js +4 -1
- package/lib/init/template/html5-repo/files/xs-app.json.hbs +1 -0
- package/lib/init/template/html5-repo/index.js +2 -0
- package/lib/init/template/http/index.js +102 -35
- package/lib/init/template/nodejs/files/package.json.hbs +1 -1
- package/lib/init/template/sample/files/nodejs/{admin-service.js → admin-service.js.hbs} +8 -1
- package/lib/init/template/sample/files/nodejs/{cat-service.js → cat-service.js.hbs} +8 -3
- package/lib/init/template/sample/index.js +4 -2
- package/lib/init/template/telemetry/files/package.json.hbs +18 -0
- package/lib/init/template/telemetry/index.js +8 -2
- package/lib/init/template/typescript/files/package.json +2 -2
- package/lib/init/template/workzone-standard/index.js +1 -1
- package/lib/init/types.d.ts +0 -6
- package/lib/watch/node-watch.js +25 -9
- package/npm-shrinkwrap.json +63 -48
- 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 '
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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(
|
|
5
|
+
const cf = require('../../../util/cf')
|
|
6
6
|
const { write, exists } = cds.utils
|
|
7
|
-
const { filterStringAsRegex } = require(
|
|
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,
|
|
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
|
-
|
|
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) {
|
|
@@ -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
|
|
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 } =
|
|
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
|
}
|
package/lib/init/types.d.ts
CHANGED
package/lib/watch/node-watch.js
CHANGED
|
@@ -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, (
|
|
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
|
-
|
|
28
|
-
if (evt.type
|
|
29
|
-
if (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 =
|
|
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')
|