@sap/cds-dk 8.7.3 → 8.8.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 +36 -1
- package/bin/bind.js +7 -8
- package/bin/cds.js +2 -4
- package/bin/compile/index.js +1 -0
- package/bin/deploy/index.js +10 -5
- package/bin/watch.js +48 -7
- package/lib/bind/{cfKeyProvider.js → cf.js} +43 -53
- package/lib/bind/k8s.js +138 -0
- package/lib/bind/plugin.js +48 -0
- package/lib/bind/shared.js +267 -0
- package/lib/build/buildTaskEngine.js +1 -1
- package/lib/build/plugins/helm/files/subcharts/content-deployment/Chart.yaml +0 -2
- package/lib/build/plugins/helm/files/subcharts/web-application/Chart.yaml +1 -3
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/service-binding.yaml +3 -0
- package/lib/build/plugins/helm/files/subcharts/web-application/values.schema.json +3 -0
- package/lib/build/plugins/helm/files/subcharts/web-application/values.yaml +2 -1
- package/lib/build/util.js +1 -1
- package/lib/compile/to-mermaid.js +1 -2
- package/lib/deploy/to-hana/hana.js +1 -1
- package/lib/deploy/to-hana/mtaUtil.js +0 -13
- package/lib/env/schemas/cds-rc.js +17 -30
- package/lib/import/common.js +20 -10
- package/lib/init/constants.js +1 -1
- package/lib/init/merge.js +41 -32
- package/lib/init/projectReader.js +15 -7
- package/lib/init/registries/mta.js +7 -1
- package/lib/init/template/approuter/files/package.json +1 -1
- package/lib/init/template/approuter/files/xs-app.json.hbs +0 -11
- package/lib/init/template/cloud-logging/files/package.json.hbs +12 -1
- package/lib/init/template/cloud-logging/files/values.yaml.hbs +0 -2
- package/lib/init/template/cloud-logging/index.js +8 -15
- package/lib/init/template/destination/files/values.yaml +4 -4
- package/lib/init/template/destination/index.js +1 -1
- package/lib/init/template/html5-repo/files/app-package.json.hbs +0 -2
- package/lib/init/template/html5-repo/files/containerize.yaml.hbs +25 -0
- package/lib/init/template/html5-repo/files/mta.yaml.hbs +1 -2
- package/lib/init/template/html5-repo/files/package.json.hbs +1 -1
- package/lib/init/template/html5-repo/files/ui-resources/package.json.hbs +11 -0
- package/lib/init/template/html5-repo/files/ui5-deploy.yaml.hbs +4 -7
- package/lib/init/template/html5-repo/files/values.yaml.hbs +6 -4
- package/lib/init/template/html5-repo/index.js +22 -10
- package/lib/init/template/http/index.js +1 -1
- package/lib/init/template/lint/index.js +7 -1
- package/lib/init/template/multitenancy/files/mta.yaml.hbs +0 -2
- package/lib/init/template/multitenancy/index.js +1 -1
- package/lib/init/template/portal/files/mta.yaml.hbs +9 -0
- package/lib/init/template/portal/index.js +3 -1
- package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +119 -119
- package/lib/init/template/sample/files/app/browse/webapp/manifest.json +1 -1
- package/lib/init/template/telemetry/index.js +0 -13
- package/lib/init/template/typer/files/package.json +3 -0
- package/lib/init/template/workzone-standard/files/cdm.json.hbs +97 -0
- package/lib/init/template/workzone-standard/files/mta.yaml.hbs +23 -0
- package/lib/init/template/workzone-standard/index.js +70 -39
- package/lib/init/template/xsuaa/files/mta.yaml.hbs +2 -2
- package/lib/init/template/xsuaa/files/values.yaml.hbs +0 -2
- package/lib/init/template/xsuaa/index.js +22 -10
- package/lib/lint/io.js +8 -4
- package/lib/mtx/util/cf.js +1 -2
- package/lib/util/cf.js +104 -84
- package/lib/util/command.js +1 -2
- package/lib/util/git.js +1 -1
- package/npm-shrinkwrap.json +53 -45
- package/package.json +1 -1
- package/bin/deploy/to-cf.js +0 -0
- package/bin/deploy/via-mta.js +0 -0
- package/lib/bind/KubernetesApiAccessor.js +0 -81
- package/lib/bind/bindingManager.js +0 -89
- package/lib/bind/bindingResolver.js +0 -54
- package/lib/bind/bindingUtil.js +0 -129
- package/lib/bind/index.js +0 -127
- package/lib/bind/kubernetesCredentialsProvider.js +0 -84
- package/lib/bind/userCdsrcJsonStorageProvider.js +0 -92
- package/lib/init/template/destination/files/package.sidecar.json +0 -7
- package/lib/init/template/html5-repo/files/package.sidecar.json +0 -7
- package/lib/init/template/telemetry/files/package.json.hbs +0 -18
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
const os = require('node:os')
|
|
2
|
+
const cds = require('../cds')
|
|
3
|
+
const { exists, read, fs, path } = cds.utils
|
|
4
|
+
const git = require('../util/git')
|
|
5
|
+
const { bold } = require('../util/term')
|
|
6
|
+
|
|
7
|
+
const KindToRequiresNameMap = {
|
|
8
|
+
xsuaa: 'auth',
|
|
9
|
+
'xsuaa-auth': 'auth',
|
|
10
|
+
'ias-auth': 'auth',
|
|
11
|
+
'hana-cloud': 'db',
|
|
12
|
+
hana: 'db',
|
|
13
|
+
'hana-mt': 'db', // For compatibility only
|
|
14
|
+
'sql-mt': 'db', // For compatibility only
|
|
15
|
+
destinations: 'destinations',
|
|
16
|
+
connectivity: 'connectivity',
|
|
17
|
+
'audit-log': 'auditlog',
|
|
18
|
+
'audit-log-to-restv2': 'auditlog',
|
|
19
|
+
'enterprise-messaging': 'messaging',
|
|
20
|
+
'enterprise-messaging-amqp': 'messaging',
|
|
21
|
+
'enterprise-messaging-http': 'messaging',
|
|
22
|
+
'enterprise-messaging-shared': 'messaging',
|
|
23
|
+
'redis-messaging': 'messaging',
|
|
24
|
+
'multitenancy': 'multitenancy'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const PreferredKinds = {
|
|
28
|
+
multitenancy: 1,
|
|
29
|
+
'hana-cloud': 1,
|
|
30
|
+
xsuaa: 1,
|
|
31
|
+
'xsuaa-auth': 1,
|
|
32
|
+
'ias-auth': 1,
|
|
33
|
+
'saas-registry': 1
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function bind(options) {
|
|
37
|
+
let on
|
|
38
|
+
switch (options.on) {
|
|
39
|
+
case 'k8s':
|
|
40
|
+
case 'kubernetes':
|
|
41
|
+
on = 'k8s'
|
|
42
|
+
break
|
|
43
|
+
case undefined:
|
|
44
|
+
case null:
|
|
45
|
+
case 'cf':
|
|
46
|
+
case 'cloudfoundry':
|
|
47
|
+
on = 'cf'
|
|
48
|
+
break
|
|
49
|
+
default:
|
|
50
|
+
throw `invalid value '${options.on}' for option --on.`
|
|
51
|
+
}
|
|
52
|
+
const plugin = require(`./${on}`)
|
|
53
|
+
|
|
54
|
+
if (!options['to-app-services']) console.log(`retrieving data from ${bold(plugin.displayName())}...`)
|
|
55
|
+
|
|
56
|
+
const services = options.to.split(/,/g)
|
|
57
|
+
const resolvedServices = await Promise.all(services.map(async service => {
|
|
58
|
+
const binding = { on, ...plugin.binding4(service) }
|
|
59
|
+
const resolved = await _resolveBinding(binding)
|
|
60
|
+
delete resolved.credentials
|
|
61
|
+
return resolved
|
|
62
|
+
}))
|
|
63
|
+
if (options.kind) {
|
|
64
|
+
resolvedServices.forEach(service => service.kind = options.kind)
|
|
65
|
+
} else {
|
|
66
|
+
_determineKinds(resolvedServices)
|
|
67
|
+
}
|
|
68
|
+
for (const resolved of resolvedServices) {
|
|
69
|
+
const { kind, binding, binding: { instance, secret } } = resolved
|
|
70
|
+
if (options.serviceArg) {
|
|
71
|
+
resolved.name = options.serviceArg
|
|
72
|
+
} else if (kind) {
|
|
73
|
+
const requiresName = KindToRequiresNameMap[kind]
|
|
74
|
+
if (!requiresName) throw new Error(`Unknown CDS service name for service ${bold(instance)}. Please specify as argument for ${bold('cds bind')}.`)
|
|
75
|
+
resolved.name = requiresName
|
|
76
|
+
}
|
|
77
|
+
resolved.name ??= `custom-service:${instance || secret}`
|
|
78
|
+
const kindText = kind ? ` with kind ${bold(kind)}` : ''
|
|
79
|
+
console.log(`binding ${bold(resolved.name)} to ${plugin.displayName()} ${_bindingText(binding)}${kindText}`)
|
|
80
|
+
}
|
|
81
|
+
return resolvedServices
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function storeBindings(resolvedServices, options) {
|
|
85
|
+
const cdsrcPrivate = '.cdsrc-private.json'
|
|
86
|
+
let jsonPath = options.out || cdsrcPrivate
|
|
87
|
+
if (jsonPath === cdsrcPrivate) {
|
|
88
|
+
await git.ensureFileIsGitignored(cdsrcPrivate)
|
|
89
|
+
}
|
|
90
|
+
jsonPath = jsonPath.replace(/~/g, os.homedir())
|
|
91
|
+
if (path.extname(jsonPath) !== '.json') {
|
|
92
|
+
jsonPath = path.join(jsonPath, '.cdsrc.json')
|
|
93
|
+
}
|
|
94
|
+
const conf = exists(jsonPath) ? await read(jsonPath) : {}
|
|
95
|
+
let cdsRoot
|
|
96
|
+
if (path.basename(jsonPath) === 'package.json') {
|
|
97
|
+
conf.cds = conf.cds || {}
|
|
98
|
+
cdsRoot = conf.cds
|
|
99
|
+
} else {
|
|
100
|
+
cdsRoot = conf
|
|
101
|
+
}
|
|
102
|
+
const profile = `[${options.for}]`
|
|
103
|
+
const features = cdsRoot.features?.[profile]
|
|
104
|
+
if (features) delete features.emulate_vcap_services
|
|
105
|
+
cdsRoot.requires ??= {}
|
|
106
|
+
const section = cdsRoot.requires[profile] ??= {}
|
|
107
|
+
|
|
108
|
+
for (const { name, kind, binding, credentials } of resolvedServices) {
|
|
109
|
+
const requireService = section[name] ??= {}
|
|
110
|
+
binding.credentials = _getServiceCredentials(binding, resolvedServices, options)
|
|
111
|
+
Object.assign(requireService, { binding: { ...binding, vcap: undefined }, credentials, kind, vcap: { name } })
|
|
112
|
+
}
|
|
113
|
+
await fs.promises.mkdir(path.dirname(jsonPath), { recursive: true })
|
|
114
|
+
await fs.promises.writeFile(jsonPath, JSON.stringify(conf, null, 2))
|
|
115
|
+
await fs.promises.chmod(jsonPath, 0o600)
|
|
116
|
+
console.log(`saving bindings to ${bold(jsonPath)} in profile ${bold(options.for)}`)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function mergeCredentials(credentials, customCredentials) {
|
|
120
|
+
if (!customCredentials) return credentials
|
|
121
|
+
for (const key in customCredentials) {
|
|
122
|
+
if (typeof customCredentials[key] === 'object') {
|
|
123
|
+
if (credentials[key] && typeof credentials[key] !== 'object') {
|
|
124
|
+
throw `Cannot merge credentials: '${key}' is not an object`
|
|
125
|
+
}
|
|
126
|
+
if (!credentials[key]) {
|
|
127
|
+
credentials[key] = {}
|
|
128
|
+
}
|
|
129
|
+
credentials[key] = mergeCredentials(credentials[key], customCredentials[key])
|
|
130
|
+
} else {
|
|
131
|
+
credentials[key] = customCredentials[key]
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return credentials
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function bindingEnv(options) {
|
|
138
|
+
const bindings = _getUnresolvedBindings(options)
|
|
139
|
+
const resolvedBindings = await _resolveBindings({ ...options, bindings, cache: {} })
|
|
140
|
+
return resolvedBindings ? { VCAP_SERVICES: _toVcapServices(resolvedBindings) } : undefined
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function updateBindings(options, { onBeforeUpdate, onAfterUpdate } = {}) {
|
|
144
|
+
const bindings = _getUnresolvedBindings(options)
|
|
145
|
+
if (onBeforeUpdate) await onBeforeUpdate()
|
|
146
|
+
let resolvedBindings
|
|
147
|
+
try {
|
|
148
|
+
resolvedBindings = await _resolveBindings({ ...options, bindings, cache: {} })
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error(error.message) // REVISIT: Why not throw here?
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
const env = resolvedBindings ? { VCAP_SERVICES: _toVcapServices(resolvedBindings) } : { VCAP_SERVICES: '{}' }
|
|
154
|
+
if (onAfterUpdate) await onAfterUpdate({ bindings: resolvedBindings, env })
|
|
155
|
+
return { bindings: resolvedBindings, env }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = { bind, storeBindings, mergeCredentials, bindingEnv, updateBindings }
|
|
159
|
+
|
|
160
|
+
/* Private helpers */
|
|
161
|
+
|
|
162
|
+
function _bindingText(binding) {
|
|
163
|
+
try { return require(`./${binding.on ?? binding.type}`).description4(binding) } catch (e) {
|
|
164
|
+
if (e.code === 'MODULE_NOT_FOUND') throw `unsupported binding type ${bold(binding.on ?? binding.type)}.`
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function _resolveBinding(binding, name) {
|
|
169
|
+
try { return require(`./${binding.on ?? binding.type}`).resolve(name, binding) } catch (e) {
|
|
170
|
+
if (e.code === 'MODULE_NOT_FOUND') throw `unsupported binding type ${bold(binding.on ?? binding.type)} for service ${bold(name)}.`
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function _determineKinds(services) {
|
|
175
|
+
const vcapServices = {}
|
|
176
|
+
|
|
177
|
+
// Build VCAP_SERVICES
|
|
178
|
+
for (const service of services) {
|
|
179
|
+
service.kindCandidates = []
|
|
180
|
+
const { vcap = {} } = service.binding || {}
|
|
181
|
+
const { label, type = label } = vcap
|
|
182
|
+
if (!type) continue
|
|
183
|
+
if (!vcapServices[type]) vcapServices[type] = []
|
|
184
|
+
vcapServices[type].push({ credentials: { dummy: 'dummy' }, ...vcap, service })
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Auto configure services based on VCAP_SERVICES
|
|
188
|
+
const cds = require('../cds')
|
|
189
|
+
const env = cds.env.for('cds', process.cwd())
|
|
190
|
+
if (!env._find_credentials_for_required_service) { // REVISIT: Proper API
|
|
191
|
+
throw new Error(`Please provide a service kind or update @sap/cds version to use the kind detection.`)
|
|
192
|
+
}
|
|
193
|
+
const requires = env.requires.kinds || {}
|
|
194
|
+
for (const [kind, service] of Object.entries(requires)) {
|
|
195
|
+
const vcapService = env._find_credentials_for_required_service(kind, service, vcapServices)
|
|
196
|
+
if (!vcapService) continue
|
|
197
|
+
const isUniqueKind = !Object.values(requires).some(otherService =>
|
|
198
|
+
otherService !== service && otherService.kind === kind
|
|
199
|
+
)
|
|
200
|
+
if (isUniqueKind) vcapService.service.kindCandidates.push(kind)
|
|
201
|
+
}
|
|
202
|
+
for (const service of services) {
|
|
203
|
+
const preferredKind = service.kindCandidates.find(kind => kind in PreferredKinds)
|
|
204
|
+
if (preferredKind) {
|
|
205
|
+
service.kind = preferredKind
|
|
206
|
+
} else if (service.kindCandidates.length === 1) {
|
|
207
|
+
service.kind = service.kindCandidates[0]
|
|
208
|
+
}
|
|
209
|
+
delete service.kindCandidates
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function _getUnresolvedBindings({ cwd = process.cwd(), env } = {}) {
|
|
214
|
+
if (!env) {
|
|
215
|
+
env = cds.env.for('cds', path.resolve(cwd)) // REVISIT: This might load cds.env too early!
|
|
216
|
+
}
|
|
217
|
+
const bindings = {}
|
|
218
|
+
const requires = env.requires || {}
|
|
219
|
+
for (const name in requires) {
|
|
220
|
+
const service = requires[name]
|
|
221
|
+
if (service?.binding && !service.binding.resolved) {
|
|
222
|
+
bindings[name] = { ...service.binding, kind: service.kind }
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return bindings
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function _resolveBindings({ bindings, cwd = process.cwd(), env, cache = {}, silent }) {
|
|
229
|
+
bindings ??= _getUnresolvedBindings({ cwd, env })
|
|
230
|
+
if (Object.keys(bindings).length === 0) return undefined
|
|
231
|
+
if (!silent) console.log(`resolving cloud service bindings...`)
|
|
232
|
+
const resolvedBindings = {}
|
|
233
|
+
await Promise.all(Object.keys(bindings).map(async name => {
|
|
234
|
+
const binding = bindings[name]
|
|
235
|
+
const cacheKey = JSON.stringify(binding)
|
|
236
|
+
let resolvedBinding
|
|
237
|
+
if (!cache[cacheKey]) {
|
|
238
|
+
resolvedBinding = cache[cacheKey] = await _resolveBinding(binding, name)
|
|
239
|
+
if (!silent) console.log(`bound ${bold(name)} to ${binding.type} ${_bindingText(binding)}`)
|
|
240
|
+
} else {
|
|
241
|
+
resolvedBinding = cache[cacheKey]
|
|
242
|
+
}
|
|
243
|
+
resolvedBindings[name] = resolvedBinding
|
|
244
|
+
}))
|
|
245
|
+
return Object.keys(resolvedBindings).length > 0 ? resolvedBindings : undefined
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function _toVcapServices(bindings) {
|
|
249
|
+
const services = {}
|
|
250
|
+
for (const name in bindings) {
|
|
251
|
+
const { binding, credentials } = bindings[name]
|
|
252
|
+
const vcap = binding.vcap ?? {}
|
|
253
|
+
const type = vcap.type ?? vcap.label
|
|
254
|
+
services[type] ??= []
|
|
255
|
+
services[type].push({ ...vcap, name, credentials })
|
|
256
|
+
}
|
|
257
|
+
return JSON.stringify(services)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function _getServiceCredentials(binding, services, options) {
|
|
261
|
+
const { to, credentials } = options
|
|
262
|
+
const { type } = binding
|
|
263
|
+
if (!credentials) return
|
|
264
|
+
const plugin = require(`./${type}`)
|
|
265
|
+
const service = plugin.service4(binding, to ?? services)
|
|
266
|
+
return credentials[service] ?? credentials
|
|
267
|
+
}
|
package/lib/build/plugins/helm/files/subcharts/web-application/templates/service-binding.yaml
CHANGED
|
@@ -33,5 +33,8 @@ spec:
|
|
|
33
33
|
{{- if $mergedParams.credentialsRotationPolicy }}
|
|
34
34
|
credentialsRotationPolicy: {{ $mergedParams.credentialsRotationPolicy | toYaml | nindent 4 }}
|
|
35
35
|
{{- end }}
|
|
36
|
+
{{- if $mergedParams.secretTemplate }}
|
|
37
|
+
secretTemplate: {{ $mergedParams.secretTemplate | toYaml | nindent 2}}
|
|
38
|
+
{{- end }}
|
|
36
39
|
{{- end }}
|
|
37
40
|
{{- end }}
|
|
@@ -14,7 +14,7 @@ global:
|
|
|
14
14
|
_internal:
|
|
15
15
|
web-application:
|
|
16
16
|
versions:
|
|
17
|
-
1.
|
|
17
|
+
1.23.0: true
|
|
18
18
|
## Will be used instead of the `.Chart.Name`, e.g. when generating the Deployment name.
|
|
19
19
|
# nameOverride: name
|
|
20
20
|
|
|
@@ -258,6 +258,7 @@ bindings:
|
|
|
258
258
|
# enabled: false
|
|
259
259
|
# hana:
|
|
260
260
|
# serviceInstanceName: hana-manual
|
|
261
|
+
# secretTemplate: -> see https://github.com/SAP/sap-btp-service-operator?tab=readme-ov-file#custom-formats
|
|
261
262
|
# portal:
|
|
262
263
|
# fromSecret: manual-secret # user-provided-service
|
|
263
264
|
|
package/lib/build/util.js
CHANGED
|
@@ -134,7 +134,7 @@ async function getDefaultModelOptions(ws = false, filter) {
|
|
|
134
134
|
workspaces.forEach(workspace => {
|
|
135
135
|
cds.root = path.join(root, workspace)
|
|
136
136
|
cds.env = cds.env.for('cds', cds.root)
|
|
137
|
-
paths = paths.concat(_filterModelPaths(cds.resolve(cds.env.requires.toggles ? wildcards : '*', false), filter).map(p => path.join(workspace, p)))
|
|
137
|
+
paths = paths.concat(_filterModelPaths(cds.resolve(cds.env.requires.toggles ? wildcards : '*', false), filter).map(p => normalizePath(path.join(workspace, p))))
|
|
138
138
|
})
|
|
139
139
|
} finally {
|
|
140
140
|
cds.root = root
|
|
@@ -114,7 +114,7 @@ module.exports = function to_mermaid (model, options={}) {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
function resolveTargets (query) {
|
|
117
|
-
if (query.
|
|
117
|
+
if (query._target) return [ query._target.name ] // simple query, like projection on
|
|
118
118
|
|
|
119
119
|
// SELECT: { from: { args: [{ ref: ['t1'] }, { ref: ['t2'] }], join: ..., on: ... } }
|
|
120
120
|
const targets = query.SELECT?.from?.args?.filter(arg => arg.ref?.length).map (arg => arg.ref[0])
|
|
@@ -205,4 +205,3 @@ function to_mta_mermaid (file) {
|
|
|
205
205
|
|
|
206
206
|
return diag
|
|
207
207
|
}
|
|
208
|
-
|
|
@@ -14,7 +14,7 @@ const mtaUtil = require('./mtaUtil');
|
|
|
14
14
|
|
|
15
15
|
const { bold, info, warn } = require('../../util/term');
|
|
16
16
|
|
|
17
|
-
const { bind, storeBindings } = require('../../bind');
|
|
17
|
+
const { bind, storeBindings } = require('../../bind/shared');
|
|
18
18
|
const { BUILD_TASK_HANA, OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM, OUTPUT_MODE_RESULT } = require('../../build/constants');
|
|
19
19
|
|
|
20
20
|
const DEBUG = cds.debug('cli');
|
|
@@ -24,18 +24,6 @@ async function getHanaDbModuleDescriptor(projectPath, moduleName) {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
async function getServiceModuleDescriptor(projectPath, moduleName, moduleType) {
|
|
28
|
-
// mta might be null if mta.yaml does not exist
|
|
29
|
-
const mta = await _getMta(projectPath)
|
|
30
|
-
const projectInfo = await _getProjectInfo(projectPath, mta)
|
|
31
|
-
const appName = _getApplicationName(mta, moduleName, moduleType)
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
project: projectInfo,
|
|
35
|
-
appName: appName ? appName : `${projectInfo.name}-${moduleName}`
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
27
|
async function _getProjectInfo(projectPath, mta) {
|
|
40
28
|
const details = {
|
|
41
29
|
}
|
|
@@ -166,6 +154,5 @@ function _findHdiResource(mta, moduleName) {
|
|
|
166
154
|
|
|
167
155
|
module.exports = {
|
|
168
156
|
getHanaDbModuleDescriptor,
|
|
169
|
-
getServiceModuleDescriptor,
|
|
170
157
|
MTA_YAML
|
|
171
158
|
}
|
|
@@ -139,12 +139,12 @@ module.exports = {
|
|
|
139
139
|
as: {
|
|
140
140
|
type: "string",
|
|
141
141
|
description: "Converts the input file into one of these supported formats: cds, csn or json.",
|
|
142
|
-
$ref: "#/$defs/
|
|
142
|
+
$ref: "#/$defs/importOptions/as"
|
|
143
143
|
},
|
|
144
144
|
from: {
|
|
145
145
|
type: "string",
|
|
146
146
|
description: "Imports the model from the one of the given options: edmx, openapi or asyncapi.",
|
|
147
|
-
$ref: "#/$defs/
|
|
147
|
+
$ref: "#/$defs/importOptions/from"
|
|
148
148
|
},
|
|
149
149
|
force: {
|
|
150
150
|
type: "boolean",
|
|
@@ -173,7 +173,7 @@ module.exports = {
|
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
},
|
|
176
|
-
|
|
176
|
+
|
|
177
177
|
export: {
|
|
178
178
|
type: "object",
|
|
179
179
|
description: "Customize cds compile settings",
|
|
@@ -191,7 +191,7 @@ module.exports = {
|
|
|
191
191
|
merged: {
|
|
192
192
|
type: "object",
|
|
193
193
|
description: "properties that needs to be there when merged option is used",
|
|
194
|
-
additionalProperties: false,
|
|
194
|
+
additionalProperties: false,
|
|
195
195
|
properties: {
|
|
196
196
|
title: {
|
|
197
197
|
type: "string",
|
|
@@ -224,37 +224,24 @@ module.exports = {
|
|
|
224
224
|
type: "object",
|
|
225
225
|
description: "Parameters related to the source of event",
|
|
226
226
|
default: {
|
|
227
|
+
region: "the region",
|
|
228
|
+
applicationNamespace: "the namespace",
|
|
229
|
+
instanceId: "the instance id"
|
|
230
|
+
},
|
|
231
|
+
additionalProperties: false,
|
|
232
|
+
properties: {
|
|
227
233
|
region: {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
"type": "string"
|
|
231
|
-
}
|
|
234
|
+
type: "string",
|
|
235
|
+
description: "The regional context of the application"
|
|
232
236
|
},
|
|
233
237
|
applicationNamespace: {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
"type": "string"
|
|
237
|
-
}
|
|
238
|
+
type: "string",
|
|
239
|
+
description: "The registered namespace of the application"
|
|
238
240
|
},
|
|
239
241
|
instanceId: {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
"type": "string"
|
|
243
|
-
}
|
|
242
|
+
type: "string",
|
|
243
|
+
description: "The instance id (tenant, installation, ...) of the application."
|
|
244
244
|
}
|
|
245
|
-
},
|
|
246
|
-
additionalProperties: false,
|
|
247
|
-
region: {
|
|
248
|
-
type: "string",
|
|
249
|
-
description: "The regional context of the application"
|
|
250
|
-
},
|
|
251
|
-
applicationNamespace: {
|
|
252
|
-
type: "string",
|
|
253
|
-
description: "The registered namespace of the application"
|
|
254
|
-
},
|
|
255
|
-
instanceId: {
|
|
256
|
-
type: "string",
|
|
257
|
-
description: "The instance id (tenant, installation, ...) of the application."
|
|
258
245
|
}
|
|
259
246
|
},
|
|
260
247
|
event_characteristics: {
|
|
@@ -332,7 +319,7 @@ module.exports = {
|
|
|
332
319
|
]
|
|
333
320
|
},
|
|
334
321
|
|
|
335
|
-
|
|
322
|
+
importOptions: {
|
|
336
323
|
from: {
|
|
337
324
|
oneOf: [
|
|
338
325
|
{
|
package/lib/import/common.js
CHANGED
|
@@ -141,11 +141,22 @@ async function isValidInputFile(inputFileLoc) {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
async function _copyToSrvExternal(file, dst, cwd) {
|
|
144
|
-
const dstDir = path.dirname(dst), srcDir = path.dirname(file);
|
|
144
|
+
const dstDir = path.dirname(dst), srcDir = path.resolve(path.dirname(file));
|
|
145
145
|
if (!file.startsWith(dstDir + path.sep)) {
|
|
146
146
|
await mkdirp(dstDir);
|
|
147
|
-
const copyOrMove = srcDir === cwd || srcDir
|
|
148
|
-
|
|
147
|
+
const copyOrMove = srcDir === cwd || srcDir === process.cwd() ? fs.renameSync : fs.copyFileSync;
|
|
148
|
+
try {
|
|
149
|
+
copyOrMove(file, dst);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
// cross-device link error (might happen in docker containers), fall back to copy and delete
|
|
152
|
+
// see cap/issues#18004
|
|
153
|
+
if (err.code === 'EXDEV') {
|
|
154
|
+
fs.copyFileSync(file, dst);
|
|
155
|
+
fs.unlinkSync(file);
|
|
156
|
+
} else {
|
|
157
|
+
throw err;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
149
160
|
return dst;
|
|
150
161
|
}
|
|
151
162
|
return file;
|
|
@@ -194,25 +205,24 @@ function _getServiceNames(csn) {
|
|
|
194
205
|
}
|
|
195
206
|
|
|
196
207
|
function _validateAndComputeOutputFilePath(outputFileLoc, as, destFilePath) {
|
|
197
|
-
let
|
|
208
|
+
let extension, expectedExtension, supportedExtension;
|
|
198
209
|
if (typeof (outputFileLoc) !== 'boolean') {
|
|
199
|
-
|
|
200
|
-
extension = outputFileLoc.substring(index + 1);
|
|
210
|
+
extension = path.extname(outputFileLoc).substring(1);
|
|
201
211
|
expectedExtension = (as) ? _getSuffix(as) : "csn";
|
|
202
|
-
supportedExtension =
|
|
212
|
+
supportedExtension = extension ? _supportedAsFormats().includes(extension) : false;
|
|
203
213
|
}
|
|
204
214
|
|
|
205
215
|
if (outputFileLoc === true || // if output file is not mentioned
|
|
206
216
|
outputFileLoc[0] === "-" || // if output file is missing and "--as" is considered as output file
|
|
207
217
|
outputFileLoc.trim() === "") {
|
|
208
218
|
throw new Error(messages.SPECIFY_OUTPUT_FILE);
|
|
209
|
-
} else if (
|
|
219
|
+
} else if (extension && !supportedExtension) {
|
|
210
220
|
// extension not supported, eg., "test.abs", "./sv/external.test.abcs", "./a."
|
|
211
221
|
throw new Error(messages.INVALID_OUTPUT_FILE);
|
|
212
|
-
} else if (
|
|
222
|
+
} else if (extension && !as && extension != "json" && extension != "csn") {
|
|
213
223
|
// --as option is not provided but output file is like: "test.cds", "./srv/external/test.cds"
|
|
214
224
|
throw new Error(messages.INCORRECT_EXTENSION);
|
|
215
|
-
} else if (
|
|
225
|
+
} else if (extension && as && extension != expectedExtension) {
|
|
216
226
|
// extension not supported by --as option, e.g., "cds import <file> --out test.yml --as cds"
|
|
217
227
|
throw new Error(messages.OUTPUT_FILE_MISMATCH);
|
|
218
228
|
} else if (supportedExtension || (!outputFileLoc.endsWith("\\") && !outputFileLoc.endsWith("/"))) {
|
package/lib/init/constants.js
CHANGED
package/lib/init/merge.js
CHANGED
|
@@ -129,12 +129,17 @@ async function _mergeJSON(into, from, project, semantics) {
|
|
|
129
129
|
* @throws {Error} Throws an error if empty target YAML is passed in
|
|
130
130
|
*/
|
|
131
131
|
async function _mergeYAML(into, from, project, semantics = {}) {
|
|
132
|
-
const target = typeof into === 'string'
|
|
133
|
-
|
|
132
|
+
const target = typeof into === 'string' ? await readYAML(into) :
|
|
133
|
+
into instanceof YAML.Document ? into :
|
|
134
|
+
new YAML.Document(into)
|
|
135
|
+
const source = typeof from === 'string' ? await readYAML(from, project) :
|
|
136
|
+
from instanceof YAML.Document ? from :
|
|
137
|
+
new YAML.Document(from)
|
|
134
138
|
|
|
135
139
|
if (!target?.contents) {
|
|
136
140
|
if (typeof into === 'string' && typeof from === 'string') {
|
|
137
|
-
project
|
|
141
|
+
if (project) await writeYAML(into, source)
|
|
142
|
+
else await copy(from, into)
|
|
138
143
|
return source
|
|
139
144
|
}
|
|
140
145
|
throw new Error(`Target YAML doesn't exist`)
|
|
@@ -143,19 +148,19 @@ async function _mergeYAML(into, from, project, semantics = {}) {
|
|
|
143
148
|
const entryMap = new Map, templateEntryMap = new Map
|
|
144
149
|
|
|
145
150
|
const { additions, overwrites, deletions, relationships } = semantics
|
|
146
|
-
|
|
151
|
+
for (const entry of additions ?? []) {
|
|
147
152
|
entryMap.set(entry, undefined)
|
|
148
153
|
templateEntryMap.set(entry, undefined)
|
|
149
|
-
}
|
|
150
|
-
|
|
154
|
+
}
|
|
155
|
+
for (const deletion of deletions ?? []) {
|
|
151
156
|
entryMap.set(deletion.item, undefined)
|
|
152
|
-
}
|
|
153
|
-
|
|
157
|
+
}
|
|
158
|
+
for (const relationship of relationships ?? []) {
|
|
154
159
|
const [entry] = relationship.into
|
|
155
160
|
const [insertEntry] = relationship.insert
|
|
156
161
|
const hash = JSON.stringify(insertEntry) + ' -> ' + JSON.stringify(entry)
|
|
157
162
|
entryMap.set(hash, undefined)
|
|
158
|
-
}
|
|
163
|
+
}
|
|
159
164
|
|
|
160
165
|
let collectionStack = [target.contents]
|
|
161
166
|
|
|
@@ -347,32 +352,36 @@ async function _mergeYAML(into, from, project, semantics = {}) {
|
|
|
347
352
|
// 7. Create missing relationships
|
|
348
353
|
_traverseYAMLNode(target.contents, null, {
|
|
349
354
|
mergeCollection: (targetNode, templateNode) => {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
targetNode.add({ [intoKey]: pair })
|
|
355
|
+
if (YAML.isSeq(templateNode)) {
|
|
356
|
+
const targetJSON = YAML.parse(String(targetNode))
|
|
357
|
+
const relationships = semantics.relationships?.filter(relationship => {
|
|
358
|
+
const [entry, keyPath] = relationship.into
|
|
359
|
+
if (!entryMap.get(entry)) return false
|
|
360
|
+
const existingNode = _getYAMLProperty(entryMap.get(entry).node, keyPath)
|
|
361
|
+
return targetNode === existingNode
|
|
362
|
+
}) ?? []
|
|
363
|
+
|
|
364
|
+
for (const { into, insert } of relationships) {
|
|
365
|
+
const intoKey = into[into.length - 1]
|
|
366
|
+
const [insEntry, entryKeyPath] = insert
|
|
367
|
+
const missingPairs = [insEntry]
|
|
368
|
+
.filter(item => {
|
|
369
|
+
if (!entryMap.get(item)) {
|
|
370
|
+
const spec = inspect(item, { colors, depth:11 })
|
|
371
|
+
throw new Error(`Did not find entry in template for ${spec} in ${from}:\n${source}`)
|
|
372
|
+
}
|
|
373
|
+
return !targetJSON.some(targetItem =>
|
|
374
|
+
_getProperty(entryMap.get(item).json, entryKeyPath) === targetItem[intoKey]
|
|
375
|
+
)
|
|
372
376
|
})
|
|
377
|
+
.map(item => entryMap.get(item).node.get(intoKey))
|
|
378
|
+
for (const pair of missingPairs) {
|
|
379
|
+
targetNode.add({ [intoKey]: pair })
|
|
380
|
+
}
|
|
373
381
|
}
|
|
382
|
+
}
|
|
374
383
|
}
|
|
375
|
-
|
|
384
|
+
}, source.contents)
|
|
376
385
|
|
|
377
386
|
typeof into === 'string' && await writeYAML(into, target)
|
|
378
387
|
return target
|