@sap/cds-dk 7.8.2 → 7.9.2
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/bin/add.js +28 -7
- package/bin/bind.js +64 -21
- package/bin/build.js +35 -3
- package/bin/cds.js +31 -13
- package/bin/compile/index.js +44 -3
- package/bin/completion/completionFs.js +61 -0
- package/bin/completion/index.js +212 -0
- package/bin/completion/scripts/cds-bash.sh +17 -0
- package/bin/completion/scripts/cds-ps.ps1 +18 -0
- package/bin/completion/scripts/cds-zsh.sh +12 -0
- package/bin/help.js +8 -2
- package/bin/init.js +34 -4
- package/bin/subscribe.js +5 -1
- package/bin/watch.js +4 -2
- package/lib/bind/KubernetesApiAccessor.js +3 -2
- package/lib/bind/bindingResolver.js +8 -4
- package/lib/bind/bindingUtil.js +10 -8
- package/lib/bind/cfKeyProvider.js +58 -153
- package/lib/bind/index.js +49 -36
- package/lib/bind/kubernetesCredentialsProvider.js +6 -5
- package/lib/bind/userCdsrcJsonStorageProvider.js +25 -3
- package/lib/build/buildTaskEngine.js +1 -1
- package/lib/build/provider/hana/index.js +1 -1
- package/lib/build/provider/hana/template/undeploy.json +3 -1
- package/lib/build/provider/nodejs/index.js +1 -1
- package/lib/compile/openapi/index.js +5 -0
- package/lib/deploy/to-hana/hana.js +25 -7
- package/lib/env/schemas/cds-rc.js +29 -2
- package/lib/import/odata/v4parser.js +78 -25
- package/lib/init/constants.js +2 -1
- package/lib/init/index.js +18 -13
- package/lib/init/merge.js +1 -0
- package/lib/init/mvn.js +8 -47
- package/lib/init/plugin.js +0 -4
- package/lib/init/projectReader.js +17 -10
- package/lib/init/template/approuter/files/values.yaml.hbs +0 -12
- package/lib/init/template/completion/completionSetup.js +184 -0
- package/lib/init/template/completion/index.js +22 -0
- package/lib/init/template/data/as-csv.js +25 -17
- package/lib/init/template/data/as-json.js +148 -44
- package/lib/init/template/data/index.js +28 -15
- package/lib/init/template/hana/files/undeploy.json +2 -1
- package/lib/init/template/helm/files/chart/values.schema.json +1 -1
- package/lib/init/template/helm/files/content-deployment/Chart.yaml +0 -2
- package/lib/init/template/helm/files/content-deployment/values.schema.json +0 -1
- package/lib/init/template/helm/files/service-instance/Chart.yaml +0 -2
- package/lib/init/template/helm/files/service-instance/values.schema.json +0 -1
- package/lib/init/template/helm/files/web-application/Chart.yaml +0 -2
- package/lib/init/template/helm/files/web-application/values.schema.json +0 -1
- package/lib/init/template/helm-unified-runtime/files/values.schema.json +1 -1
- package/lib/init/template/http/index.js +150 -104
- package/lib/init/template/kafka/index.js +6 -0
- package/lib/init/template/lint/index.js +12 -13
- package/lib/init/template/mta/files/mta.yaml.hbs +2 -2
- package/lib/init/template/multitenancy/files/values.yaml.hbs +0 -5
- package/lib/init/template/multitenancy/index.js +13 -5
- package/lib/init/template/nodejs/files/.vscode/settings.json +11 -0
- package/lib/init/template/nodejs/files/eslint.config.js +13 -0
- package/lib/init/template/nodejs/files/package.json.hbs +3 -1
- package/lib/init/template/sample/index.js +18 -20
- package/lib/init/template/tiny-sample/index.js +1 -2
- package/lib/init/template/typer/index.js +2 -2
- package/lib/lint/Linter.js +133 -55
- package/lib/lint/checks.js +5 -6
- package/lib/lint/io.js +119 -10
- package/lib/mtx/auth_manager.js +5 -5
- package/lib/mtx/params.js +4 -2
- package/lib/mtx/settings_manager.js +1 -1
- package/lib/mtx/tenant_action.js +12 -2
- package/lib/mtx/util/logging.js +10 -4
- package/lib/util/axios.js +2 -1
- package/lib/util/cf.js +32 -37
- package/lib/util/git.js +1 -1
- package/lib/util/term.js +1 -1
- package/{bin → lib/watch}/watched.js +2 -2
- package/npm-shrinkwrap.json +35 -52
- package/package.json +2 -2
- package/lib/init/template/nodejs/files/.eslintrc +0 -26
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const cds = require('../../../cds')
|
|
2
2
|
const { exists, fs } = cds.utils
|
|
3
|
+
const { dim } = require('../../../util/term')
|
|
3
4
|
const { resolve, join, relative } = require('path')
|
|
4
5
|
const { URLSearchParams } = require('url')
|
|
5
6
|
const { env4 } = require('../../projectReader')
|
|
@@ -14,8 +15,8 @@ module.exports = class DataTemplate extends require('../../plugin') {
|
|
|
14
15
|
type: 'string',
|
|
15
16
|
short: 'f',
|
|
16
17
|
help: `Filter for entities matching the given pattern. If it contains meta
|
|
17
|
-
characters like '^' or '*', it is treated
|
|
18
|
-
otherwise as
|
|
18
|
+
characters like '^' or '*', it is treated as a regular expression,
|
|
19
|
+
otherwise as an include pattern, i.e /.*pattern.*/i`
|
|
19
20
|
},
|
|
20
21
|
'data:for': {
|
|
21
22
|
type: 'string',
|
|
@@ -52,8 +53,9 @@ module.exports = class DataTemplate extends require('../../plugin') {
|
|
|
52
53
|
if (Number.isNaN(records) || records < 1) records = 1
|
|
53
54
|
|
|
54
55
|
const [filterName, filterQueryStr] = (cds.cli.options['filter'] ?? cds.cli.options['data:for'] ?? '').split(':')
|
|
55
|
-
const nameFilter =
|
|
56
|
+
const nameFilter = filterStringAsRegex(filterName)
|
|
56
57
|
const dataForParams = parseQueryStr(filterQueryStr)
|
|
58
|
+
const { queries } = dataForParams
|
|
57
59
|
|
|
58
60
|
const env = env4('production')
|
|
59
61
|
let dest = (typeof out === 'string') // target folder
|
|
@@ -68,33 +70,43 @@ module.exports = class DataTemplate extends require('../../plugin') {
|
|
|
68
70
|
|
|
69
71
|
const data = csn.all('entity')
|
|
70
72
|
.filter (e => e.name.match(nameFilter)) // --for prefix|regex
|
|
73
|
+
.filter (e => queries || !e.query) // remove all projection-like entities unless specified differently
|
|
71
74
|
.reduce((all, e) => { all[e.name] = []; return all }, {})
|
|
72
75
|
|
|
73
76
|
await asJson(data, csn, records)
|
|
74
77
|
|
|
75
78
|
if (contentType === 'csv') {
|
|
76
79
|
const headerOnly = !cds.cli.options['records'] // compat to old behavior which only creates headers
|
|
77
|
-
|
|
78
|
-
await asCsv(data, csn, Object.assign({ headerOnly, queries }, dataForParams))
|
|
80
|
+
await asCsv(data, csn, Object.assign({ headerOnly }, dataForParams))
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
// write files
|
|
82
|
-
for (const name of Object.keys(data)) {
|
|
84
|
+
for (const name of Object.keys(data).sort()) {
|
|
83
85
|
writeFileFor(csn.definitions[name], data[name], csn, dest, force)
|
|
84
86
|
}
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
module.exports.asJson = asJson
|
|
88
90
|
module.exports.asCsv = asCsv
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
module.exports.filterStringAsRegex = filterStringAsRegex
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Turns a filter string with optional meta characters into a regular expression.
|
|
95
|
+
* If no meta characters are found, the string is treated as an include pattern.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} spec the filter string
|
|
98
|
+
* @returns {RegExp} the regular expression
|
|
99
|
+
*/
|
|
100
|
+
function filterStringAsRegex(spec) {
|
|
91
101
|
if (typeof spec === 'string') {
|
|
92
102
|
try {
|
|
93
|
-
if (spec.match(/[\^$|*]/))
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
103
|
+
if (spec.match(/[\^$|*]/)) {
|
|
104
|
+
spec = spec.replace(/^\*/, '.*') // * at start would be invalid, so make it a .*
|
|
105
|
+
return new RegExp(spec, 'i')
|
|
106
|
+
}
|
|
107
|
+
else { // no meta chars -> include semantics
|
|
108
|
+
spec = spec.replace(/\./g, '\\.') // escape literal dot
|
|
109
|
+
return new RegExp(spec, 'i')
|
|
98
110
|
}
|
|
99
111
|
} catch (err) {
|
|
100
112
|
throw err.message // user error, so cut off stack trace
|
|
@@ -149,7 +161,7 @@ function writeFileFor (entity, data, csn, dest, force) {
|
|
|
149
161
|
|
|
150
162
|
// continue only if file not already exists, or '--force' option provided
|
|
151
163
|
if (fileExists && !force) {
|
|
152
|
-
console.log(`Skipping
|
|
164
|
+
console.log(` ${dim('Skipping ' + relFilePath)}`)
|
|
153
165
|
return
|
|
154
166
|
}
|
|
155
167
|
|
|
@@ -158,7 +170,8 @@ function writeFileFor (entity, data, csn, dest, force) {
|
|
|
158
170
|
if (data.length) {
|
|
159
171
|
if (!exists(dest)) fs.mkdirSync(dest, {recursive: true})
|
|
160
172
|
fs.writeFileSync(dataFilePath, data)
|
|
161
|
-
fileExists
|
|
173
|
+
if (fileExists) console.log(` ${dim('Overwriting ' + relFilePath)}`)
|
|
174
|
+
else console.log(` ${dim('Creating ' + relFilePath)}`)
|
|
162
175
|
}
|
|
163
176
|
}
|
|
164
177
|
|
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
],
|
|
188
188
|
"properties": {
|
|
189
189
|
"fromSecret": {
|
|
190
|
-
"description": "Name of a Kubernetes Secret, with the binding content, compliant to the SAP Kubernetes Service Binding spec
|
|
190
|
+
"description": "Name of a Kubernetes Secret, with the binding content, compliant to the SAP Kubernetes Service Binding spec",
|
|
191
191
|
"$ref": "#/definitions/KubernetesName"
|
|
192
192
|
}
|
|
193
193
|
}
|
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
],
|
|
188
188
|
"properties": {
|
|
189
189
|
"fromSecret": {
|
|
190
|
-
"description": "Name of a Kubernetes Secret, with the binding content, compliant to the SAP Kubernetes Service Binding spec
|
|
190
|
+
"description": "Name of a Kubernetes Secret, with the binding content, compliant to the SAP Kubernetes Service Binding spec",
|
|
191
191
|
"$ref": "#/definitions/KubernetesName"
|
|
192
192
|
}
|
|
193
193
|
}
|
|
@@ -1,21 +1,26 @@
|
|
|
1
|
+
const https = require('node:https')
|
|
2
|
+
const axios = require('axios')
|
|
3
|
+
const asJson = require("../data/as-json")
|
|
1
4
|
const cds = require('../../../cds')
|
|
2
5
|
const cf = require("../../../util/cf")
|
|
3
6
|
const { write, exists } = cds.utils
|
|
7
|
+
const { filterStringAsRegex } = require("../data/index")
|
|
4
8
|
|
|
5
9
|
module.exports = class HttpTemplate extends require('../../plugin') {
|
|
6
10
|
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#defaultDirName = "*"
|
|
11
|
+
#nodeHost = "http://localhost:4004"
|
|
12
|
+
#javaHost = "http://localhost:8080"
|
|
10
13
|
#defaultOutputDir = "http"
|
|
11
|
-
#defaultAuth = `Authorization: Basic
|
|
14
|
+
#defaultAuth = `Authorization: Basic alice:`
|
|
12
15
|
|
|
13
16
|
options() {
|
|
14
17
|
return {
|
|
15
18
|
'filter': {
|
|
16
19
|
type: 'string',
|
|
17
20
|
short: 'f',
|
|
18
|
-
help: `
|
|
21
|
+
help: `Filter for services or entities or actions matching the given pattern. If it contains meta
|
|
22
|
+
characters like '^' or '*', it is treated as a regular expression,
|
|
23
|
+
otherwise as an include pattern, i.e /.*pattern.*/i`
|
|
19
24
|
},
|
|
20
25
|
'for-app': {
|
|
21
26
|
type: 'string',
|
|
@@ -26,146 +31,187 @@ module.exports = class HttpTemplate extends require('../../plugin') {
|
|
|
26
31
|
type: 'string',
|
|
27
32
|
short: 'o',
|
|
28
33
|
help: 'The output directory. By default, an `http` dir will be created in one of the following: test/tests/__tests__, whichever already exists. If none exists, an `http` dir will be created at the root level.'
|
|
34
|
+
},
|
|
35
|
+
'dry': {
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
help: 'Print the generated requests to the console instead of writing them to a file.'
|
|
29
38
|
}
|
|
30
39
|
}
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
async run() {
|
|
34
|
-
|
|
35
|
-
const path = cds.cli.options['filter'] || this.#defaultDirName
|
|
36
|
-
const csn = await cds.load(path)
|
|
43
|
+
const csn = await cds.load(cds.env.roots).then(cds.minify)
|
|
37
44
|
cds.linked(csn)
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
let
|
|
46
|
+
const serviceInfo = cds.compile.to.serviceinfo(csn)
|
|
47
|
+
let [hostname, auth] = await this.#getHostnameAndAuth(serviceInfo)
|
|
48
|
+
let services = this.#collectServiceInfo(csn, serviceInfo)
|
|
49
|
+
if (!services) return
|
|
42
50
|
|
|
43
|
-
this.#
|
|
51
|
+
const payload = await this.#generatePayload(csn, services)
|
|
52
|
+
this.#generateRequests(services, hostname, auth, payload)
|
|
44
53
|
}
|
|
45
54
|
|
|
46
|
-
async #getHostnameAndAuth() {
|
|
55
|
+
async #getHostnameAndAuth(serviceInfo) {
|
|
47
56
|
if (!cds.cli.options['for-app']) {
|
|
48
|
-
|
|
57
|
+
if (serviceInfo[0].runtime === 'Java') {
|
|
58
|
+
return [this.#javaHost, null]
|
|
59
|
+
} else {
|
|
60
|
+
return [this.#nodeHost, this.#defaultAuth]
|
|
61
|
+
}
|
|
49
62
|
}
|
|
50
63
|
|
|
51
|
-
const
|
|
64
|
+
const app = cds.cli.options['for-app']
|
|
65
|
+
// Later: differentiate between CF and Kyma
|
|
66
|
+
const env = await cf.getEnv(app)
|
|
67
|
+
if (!cds.cli.options.dry) console.error('Fetching credentials from Cloud Foundry app \'' + app + '\'...')
|
|
52
68
|
const hostname = `https://${env.application_env_json.VCAP_APPLICATION.application_uris[0]}`
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const { access_token } = await response.json()
|
|
66
|
-
const auth = `x-approuter-authorization: bearer ` + access_token
|
|
67
|
-
return [hostname, auth]
|
|
68
|
-
} else {
|
|
69
|
-
throw new Error('Error authenticating ', { url, response })
|
|
70
|
-
}
|
|
69
|
+
const xsuaaCredentials = env.system_env_json.VCAP_SERVICES?.xsuaa[0]?.credentials ?? {}
|
|
70
|
+
const { url, clientid, clientsecret, certurl, certificate, key } = xsuaaCredentials
|
|
71
|
+
if (!url) throw new Error('No XSUAA service found in the environment of ' + app)
|
|
72
|
+
|
|
73
|
+
const auth = certificate ? { maxRedirects: 0, httpsAgent: new https.Agent({ cert: certificate, key }) }
|
|
74
|
+
: { auth: { username: clientid, password: clientsecret } }
|
|
75
|
+
const authUrl = `${certurl ?? url}/oauth/token`
|
|
76
|
+
const data = `grant_type=client_credentials&client_id=${encodeURI(clientid)}`
|
|
77
|
+
const config = { method: 'POST', timeout: 5000, data, ...auth }
|
|
78
|
+
const { access_token } = (await axios(authUrl, config)).data
|
|
79
|
+
const approuterAuth = `x-approuter-authorization: bearer ${access_token}`
|
|
80
|
+
return [hostname, approuterAuth]
|
|
71
81
|
}
|
|
72
82
|
|
|
73
|
-
#
|
|
74
|
-
const
|
|
83
|
+
#collectServiceInfo(csn, serviceInfo) {
|
|
84
|
+
const services = new Map()
|
|
85
|
+
|
|
86
|
+
const filter = cds.cli.options['filter']
|
|
75
87
|
|
|
76
88
|
csn.services.map((s) => {
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
let
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
const sInfo = serviceInfo.find(info => info.name === s.name)
|
|
90
|
+
const urlPaths = sInfo?.endpoints?.map(e => e.path) ?? [sInfo?.urlPath]
|
|
91
|
+
if (!urlPaths) return
|
|
92
|
+
let entities = []
|
|
93
|
+
let actions = []
|
|
94
|
+
|
|
95
|
+
for (const c of csn.childrenOf(s.name)) {
|
|
96
|
+
if (c.name.match(filterStringAsRegex(filter))) {
|
|
97
|
+
if (c.name.includes('.texts')) continue
|
|
98
|
+
c.kind === 'entity' ? entities.push(this.#addNameAndReadOnly(c)) : c.kind === 'action' ? actions.push(c) : null
|
|
85
99
|
}
|
|
86
|
-
entityInfo.push(this.#addNameAndReadOnly(entities[e]))
|
|
87
100
|
}
|
|
88
|
-
|
|
89
|
-
|
|
101
|
+
if (entities.length === 0 && actions.length === 0) return
|
|
102
|
+
services.set(s.name, { entities, urlPaths, actions })
|
|
90
103
|
})
|
|
91
104
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
105
|
+
if (services.size === 0) {
|
|
106
|
+
console.error(`No services found for filter ${filter}`)
|
|
107
|
+
process.exitCode = 1
|
|
108
|
+
return
|
|
109
|
+
}
|
|
95
110
|
|
|
96
|
-
return
|
|
111
|
+
return services
|
|
97
112
|
}
|
|
98
113
|
|
|
99
|
-
#
|
|
100
|
-
return
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
: ["odata"]
|
|
114
|
+
#addNameAndReadOnly(e) {
|
|
115
|
+
return {
|
|
116
|
+
name: e.name,
|
|
117
|
+
readOnly: (e["@readonly"] || e["@cds.autoexpose"] || e['@odata.singleton']) ?? false,
|
|
118
|
+
}
|
|
105
119
|
}
|
|
106
120
|
|
|
107
|
-
#
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const serviceName = s.name.replace("Service", "").toLowerCase()
|
|
113
|
-
|
|
114
|
-
let prefix = ""
|
|
115
|
-
if (protocols) {
|
|
116
|
-
for (const p of protocols) {
|
|
117
|
-
if (p === "odata-v2") {
|
|
118
|
-
prefix = "/odata/v2/"
|
|
119
|
-
} else if (p === "rest") {
|
|
120
|
-
prefix = "/rest/"
|
|
121
|
-
} else {
|
|
122
|
-
prefix = this.#defaultPathPrefix
|
|
123
|
-
}
|
|
124
|
-
paths.push(prefix + serviceName)
|
|
125
|
-
}
|
|
121
|
+
async #generatePayload(csn, services) {
|
|
122
|
+
const payload = {}
|
|
123
|
+
for (const [, service] of services) {
|
|
124
|
+
for (const e of service.entities) {
|
|
125
|
+
payload[e.name] = []
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
-
return
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
name: e.name.split(".")[1],
|
|
134
|
-
readOnly: (e["@readonly"] || e["@cds.autoexpose"] || e['@odata.singleton']) ?? false,
|
|
128
|
+
// csn.all('entity').reduce((all, e) => { all[e.name] = []; return all }, {})
|
|
129
|
+
await asJson(payload, csn, 1)
|
|
130
|
+
for (const p in payload) {
|
|
131
|
+
delete payload[p][0].texts
|
|
132
|
+
delete payload[p][0].localized
|
|
135
133
|
}
|
|
134
|
+
return payload
|
|
136
135
|
}
|
|
137
136
|
|
|
138
|
-
#generateRequests(
|
|
139
|
-
for
|
|
137
|
+
#generateRequests(services, hostname, auth, payload) {
|
|
138
|
+
let varsDefined = false // when writing requests for multiple services to stdout, define vars only once
|
|
139
|
+
for (const [serviceName, service] of services) {
|
|
140
140
|
let requests = []
|
|
141
|
+
if (!cds.cli.options.dry || !varsDefined) {
|
|
142
|
+
requests.push(auth ? `@server = ${hostname}\n@auth = ${auth}\n` : `@server = ${hostname}\n`)
|
|
143
|
+
}
|
|
144
|
+
varsDefined = true
|
|
145
|
+
const headers = auth ? `Content-Type: application/json\n{{auth}}` : `Content-Type: application/json`
|
|
141
146
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const url = `{{server}}${path}/${e.name}`
|
|
147
|
-
const contentType = `Content-Type: application/json`
|
|
148
|
-
const authHeader = `{{auth}}`
|
|
149
|
-
|
|
150
|
-
requests.push(`GET ${url}\n${authHeader}\n${contentType}\n`)
|
|
147
|
+
service.urlPaths.forEach((urlPath) => {
|
|
148
|
+
if (service.urlPaths.length > 1) {
|
|
149
|
+
requests.push(`\n# Service endpoint: ${urlPath}`)
|
|
150
|
+
}
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
service.entities.forEach((e) => {
|
|
153
|
+
const entityNameSimple = e.name.slice(e.name.lastIndexOf('.') + 1)
|
|
154
|
+
const url = `{{server}}/${urlPath}${entityNameSimple}`
|
|
155
|
+
const requestTitle = `\n### ${e.name}`
|
|
156
|
+
requests.push(`${requestTitle}\nGET ${url}\n${headers}\n`)
|
|
157
|
+
if (e.readOnly) {
|
|
158
|
+
return
|
|
159
|
+
}
|
|
155
160
|
|
|
156
|
-
|
|
157
|
-
|
|
161
|
+
const entityData = payload[`${e.name}`] ? payload[`${e.name}`][0] : null
|
|
162
|
+
if (!entityData) return
|
|
163
|
+
// TODO handle ids that are not named ID
|
|
164
|
+
if (!Object.keys(entityData).some(k => k === "ID")) return // no need to write requests for entities without ids
|
|
165
|
+
|
|
166
|
+
const postBody = JSON.stringify(entityData, null, 2)
|
|
167
|
+
requests.push(`${requestTitle}\nPOST ${url}\n${headers}\n\n${postBody}\n`)
|
|
168
|
+
requests.push(`${requestTitle}\nPATCH ${url}/${entityData.ID}\n${headers}\n\n${postBody}\n`)
|
|
169
|
+
requests.push(`${requestTitle}\nDELETE ${url}/${entityData.ID}\n${headers}\n`)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
service.actions.forEach((a) => {
|
|
173
|
+
const url = `{{server}}/${urlPath}${a.name.split(".")[1]}`
|
|
174
|
+
|
|
175
|
+
let actionPayload = {}
|
|
176
|
+
if (a.params) {
|
|
177
|
+
for (const [key, value] of Object.entries(a.params)) {
|
|
178
|
+
actionPayload[key] = value.type === 'cds.Integer' ? 1
|
|
179
|
+
: value.type === 'string' ? 'sample string'
|
|
180
|
+
// TODO reference an object that has been generated
|
|
181
|
+
: typeof value.type === 'object' && value.type.ref ? payload[value.type.ref[0]][0][value.type.ref[1]]
|
|
182
|
+
: null
|
|
183
|
+
}
|
|
158
184
|
}
|
|
159
|
-
|
|
185
|
+
requests.push(`\n### ${a.name}\nPOST ${url}\n${headers}\n\n${JSON.stringify(actionPayload, null, 2)}\n`)
|
|
186
|
+
})
|
|
187
|
+
|
|
160
188
|
})
|
|
189
|
+
this.#write(requests, serviceName)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
161
192
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
193
|
+
#write(requests, serviceName) {
|
|
194
|
+
const http = requests.join('\n')
|
|
195
|
+
if (cds.cli.options.dry) {
|
|
196
|
+
console.log(http)
|
|
197
|
+
} else {
|
|
198
|
+
const term = require('../../../util/term')
|
|
199
|
+
const outputPath = this.#determineOutputPath(serviceName)
|
|
200
|
+
if (exists(outputPath)) {
|
|
201
|
+
if (cds.cli.options.force) {
|
|
202
|
+
console.log(term.dim(` Overwriting ${outputPath}`))
|
|
203
|
+
} else {
|
|
204
|
+
console.log(term.dim(` Skipping ${outputPath}`))
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
console.log(term.dim(` Creating ${outputPath}`))
|
|
209
|
+
}
|
|
210
|
+
write(http).to(outputPath)
|
|
165
211
|
}
|
|
166
212
|
}
|
|
167
213
|
|
|
168
|
-
#determineOutputPath(
|
|
214
|
+
#determineOutputPath(serviceName) {
|
|
169
215
|
let outputDir
|
|
170
216
|
const { out } = cds.cli.options
|
|
171
217
|
if (out) {
|
|
@@ -175,7 +221,7 @@ module.exports = class HttpTemplate extends require('../../plugin') {
|
|
|
175
221
|
} else {
|
|
176
222
|
outputDir = this.#defaultOutputDir
|
|
177
223
|
}
|
|
178
|
-
return `${outputDir}/${
|
|
224
|
+
return `${outputDir}/${serviceName}.http`
|
|
179
225
|
}
|
|
180
226
|
}
|
|
181
227
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const cds = require('../../../cds')
|
|
2
|
+
const { exists, fs, path } = cds.utils
|
|
1
3
|
const { readProject } = require('../../projectReader')
|
|
2
4
|
const { merge } = require('../../merge')
|
|
3
5
|
const { srv4, kafkaMessaging } = require('../../registries/mta')
|
|
@@ -6,6 +8,10 @@ const mvn = require('../../mvn')
|
|
|
6
8
|
module.exports = class KafkaTemplate extends require('../../plugin') {
|
|
7
9
|
|
|
8
10
|
static hasInProduction(env) {
|
|
11
|
+
if (exists('pom.xml')) {
|
|
12
|
+
if (!exists('srv/pom.xml')) return false
|
|
13
|
+
return fs.readFileSync(path.resolve(cds.root, 'srv/pom.xml'), 'utf8').includes('cds-feature-kafka')
|
|
14
|
+
}
|
|
9
15
|
return !!(env.requires?.messaging?.kind === 'kafka')
|
|
10
16
|
}
|
|
11
17
|
|
|
@@ -11,7 +11,7 @@ const cds = require("../../../cds");
|
|
|
11
11
|
const { mkdirp, exists } = cds.utils;
|
|
12
12
|
const IS_WIN = os.platform() === "win32";
|
|
13
13
|
const VSCODE_FILE_ASSOCIATIONS = {
|
|
14
|
-
"csv": [
|
|
14
|
+
"csv": ["csv", "csv (semicolon)", "tsv", "tab"]
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
module.exports = class LintTemplate extends require('../../plugin') {
|
|
@@ -43,7 +43,7 @@ module.exports = class LintTemplate extends require('../../plugin') {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
async run() {
|
|
46
|
-
// Require ESLint v>=
|
|
46
|
+
// Require ESLint v>=8.0.0
|
|
47
47
|
if (!this.isTest) {
|
|
48
48
|
this._checkEslint();
|
|
49
49
|
}
|
|
@@ -69,21 +69,20 @@ module.exports = class LintTemplate extends require('../../plugin') {
|
|
|
69
69
|
// Generate VS Code extension settings
|
|
70
70
|
const { getFileExtensions } = require("@sap/eslint-plugin-cds/lib/api");
|
|
71
71
|
const ALLOWED_FILE_EXTENSIONS = getFileExtensions();
|
|
72
|
-
|
|
72
|
+
const extensions = new Set(ALLOWED_FILE_EXTENSIONS.map((ext) => {
|
|
73
73
|
return path.extname(ext).replace('.', '');
|
|
74
|
-
})
|
|
74
|
+
}))
|
|
75
75
|
extensions.forEach((ext) => {
|
|
76
76
|
if (ext in VSCODE_FILE_ASSOCIATIONS) {
|
|
77
77
|
VSCODE_FILE_ASSOCIATIONS[ext].forEach((assoc) => {
|
|
78
|
-
extensions.
|
|
78
|
+
extensions.add(assoc)
|
|
79
79
|
})
|
|
80
80
|
}
|
|
81
81
|
});
|
|
82
82
|
await io.sanitizeVscodeSettings(
|
|
83
|
-
cds.root,
|
|
84
83
|
path.join(cds.root, ".vscode/settings.json"),
|
|
85
84
|
this.lintType,
|
|
86
|
-
extensions,
|
|
85
|
+
Array.from(extensions),
|
|
87
86
|
this.customRuleExample
|
|
88
87
|
);
|
|
89
88
|
}
|
|
@@ -124,8 +123,8 @@ module.exports = class LintTemplate extends require('../../plugin') {
|
|
|
124
123
|
_checkEslint() {
|
|
125
124
|
const eslintCmd = checks.resolveEslint(cds.root, '');
|
|
126
125
|
if (!eslintCmd) {
|
|
127
|
-
this.missingNpmDependencies["names"].push("eslint");
|
|
128
|
-
this.missingNpmDependencies["msgs"].push("ESLint v>=
|
|
126
|
+
this.missingNpmDependencies["names"].push("eslint@^9");
|
|
127
|
+
this.missingNpmDependencies["msgs"].push("ESLint v>=9.0.0");
|
|
129
128
|
}
|
|
130
129
|
return;
|
|
131
130
|
}
|
|
@@ -135,10 +134,11 @@ module.exports = class LintTemplate extends require('../../plugin') {
|
|
|
135
134
|
"@sap/eslint-plugin-cds",
|
|
136
135
|
cds.root
|
|
137
136
|
);
|
|
137
|
+
|
|
138
138
|
// Require locally installed ESLint CDS plugin
|
|
139
139
|
if (!eslintPluginLocal) {
|
|
140
|
-
this.missingNpmDependencies["names"].push("@sap/eslint-plugin-cds");
|
|
141
|
-
this.missingNpmDependencies["msgs"].push("ESLint plugin for CDS");
|
|
140
|
+
this.missingNpmDependencies["names"].push("@sap/eslint-plugin-cds@^3.0.0");
|
|
141
|
+
this.missingNpmDependencies["msgs"].push("ESLint plugin for CDS v>=3.0.0");
|
|
142
142
|
}
|
|
143
143
|
return;
|
|
144
144
|
}
|
|
@@ -153,10 +153,9 @@ module.exports = class LintTemplate extends require('../../plugin') {
|
|
|
153
153
|
const { getConfigPath } = require("@sap/eslint-plugin-cds/lib/api");
|
|
154
154
|
let configPath = getConfigPath(cds.root);
|
|
155
155
|
if (!configPath) {
|
|
156
|
-
configPath = path.join(cds.root, ".
|
|
156
|
+
configPath = path.join(cds.root, "eslint.config.js");
|
|
157
157
|
}
|
|
158
158
|
await io.sanitizeEslintConfig(configPath, this.customRuleExample);
|
|
159
|
-
return;
|
|
160
159
|
}
|
|
161
160
|
|
|
162
161
|
async _promptInstallDependencies(task) {
|
|
@@ -27,13 +27,13 @@ modules:
|
|
|
27
27
|
{{/isNodejs}}
|
|
28
28
|
{{#isJava}}
|
|
29
29
|
parameters:
|
|
30
|
-
buildpack:
|
|
30
|
+
buildpack: sap_java_buildpack_jakarta
|
|
31
31
|
readiness-health-check-type: http
|
|
32
32
|
readiness-health-check-http-endpoint: /
|
|
33
33
|
properties:
|
|
34
34
|
SPRING_PROFILES_ACTIVE: cloud,sandbox
|
|
35
35
|
JBP_CONFIG_COMPONENTS: "jres: ['com.sap.xs.java.buildpack.jre.SAPMachineJRE']"
|
|
36
|
-
JBP_CONFIG_SAP_MACHINE_JRE: '{
|
|
36
|
+
JBP_CONFIG_SAP_MACHINE_JRE: '{ version: {{jdkVersion}}.+ }'
|
|
37
37
|
build-parameters:
|
|
38
38
|
builder: custom
|
|
39
39
|
commands:
|