@sap/cds-dk 8.4.2 → 8.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -1
- package/bin/debug.js +90 -0
- package/bin/watch.js +12 -2
- package/lib/bind/userCdsrcJsonStorageProvider.js +0 -19
- package/lib/build/buildTaskEngine.js +1 -1
- 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/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 +4 -5
- package/lib/init/index.js +17 -25
- package/lib/init/merge.js +1 -4
- package/lib/init/projectReader.js +75 -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/http/index.js +102 -35
- 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 +1 -1
- 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 +26 -31
- package/package.json +1 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
srv:
|
|
3
|
+
bindings:
|
|
4
|
+
cloud-logging:
|
|
5
|
+
serviceInstanceName: cloud-logging
|
|
6
|
+
credentialsRotationPolicy:
|
|
7
|
+
enabled: true
|
|
8
|
+
rotationFrequency: 168h
|
|
9
|
+
cloud-logging:
|
|
10
|
+
serviceOfferingName: cloud-logging
|
|
11
|
+
servicePlanName: standard
|
|
12
|
+
parameters:
|
|
13
|
+
ingest_otlp:
|
|
14
|
+
enabled: true
|
|
15
|
+
|
|
16
|
+
|
|
@@ -7,7 +7,7 @@ const { srv4, mtxSidecar4, approuter } = require('../../registries/mta')
|
|
|
7
7
|
module.exports = class extends require('../../plugin') {
|
|
8
8
|
|
|
9
9
|
static hasInProduction(env) {
|
|
10
|
-
return env.requires?.
|
|
10
|
+
return env.requires?.['cloud-logging'];
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
options() {
|
|
@@ -24,23 +24,23 @@ module.exports = class extends require('../../plugin') {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
async canRun() {
|
|
27
|
-
const { hasMta, hasHelm, hasHelmUnifiedRuntime } = readProject()
|
|
28
|
-
if ((hasHelmUnifiedRuntime || hasHelm) && !hasMta) throw `'cds add cloud-logging' is not available for Kyma yet`
|
|
29
27
|
return true
|
|
30
28
|
}
|
|
31
29
|
|
|
30
|
+
requires() {
|
|
31
|
+
const project = readProject(), { hasTelemetry } = project
|
|
32
|
+
if ((cds.cli.options['with-telemetry'] || hasTelemetry)) return ['telemetry']
|
|
33
|
+
}
|
|
34
|
+
|
|
32
35
|
async run() {
|
|
33
|
-
const project = readProject()
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
await merge(__dirname, 'files/package.json.hbs').into('package.json', { with: project })
|
|
37
|
-
await sort('package.json', 'dependencies')
|
|
38
|
-
}
|
|
36
|
+
const project = readProject();
|
|
37
|
+
await merge(__dirname, 'files/package.json.hbs').into('package.json', { with: project })
|
|
38
|
+
await sort('package.json', 'dependencies')
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
async combine() {
|
|
42
42
|
const project = readProject()
|
|
43
|
-
const { hasMta, hasApprouter, isJava, hasMultitenancy, srvPath } = project
|
|
43
|
+
const { hasMta, hasHelm, hasHelmUnifiedRuntime, hasApprouter, isJava, hasMultitenancy, srvPath } = project
|
|
44
44
|
|
|
45
45
|
if (hasMta) {
|
|
46
46
|
const cloudLogging = {
|
|
@@ -77,5 +77,9 @@ module.exports = class extends require('../../plugin') {
|
|
|
77
77
|
with: project, additions, relationships
|
|
78
78
|
})
|
|
79
79
|
}
|
|
80
|
+
if (hasHelm || hasHelmUnifiedRuntime) {
|
|
81
|
+
await merge(__dirname, 'files/values.yaml.hbs').into('chart/values.yaml')
|
|
82
|
+
}
|
|
80
83
|
}
|
|
81
84
|
}
|
|
85
|
+
|
|
@@ -80,7 +80,7 @@ function fillRecord(record, recordID, artifact, csn, data, parent, depth, refere
|
|
|
80
80
|
Object.defineProperty(record, '$id', {value: recordID, enumerable: false})
|
|
81
81
|
if (data[artifact.name]) data[artifact.name].push(record) // add id to data, to be referenced by associations to prevent circular dependencies
|
|
82
82
|
|
|
83
|
-
const elems = Object.values(artifact.elements ?? artifact.params).sort(asJson.elementsSorter(csn))
|
|
83
|
+
const elems = Object.values(artifact.elements ?? artifact.params ?? {}).sort(asJson.elementsSorter(csn))
|
|
84
84
|
elems.forEach(el => {
|
|
85
85
|
record[el.name] = dataForField(el, artifact, csn, record, data, parent, depth, referenceData)
|
|
86
86
|
})
|
|
@@ -6,6 +6,7 @@ const { URLSearchParams } = require('url')
|
|
|
6
6
|
const { env4 } = require('../../projectReader')
|
|
7
7
|
const asJson = require('./as-json')
|
|
8
8
|
const asCsv = require('./as-csv')
|
|
9
|
+
const { filterStringAsRegex } = require('../../add')
|
|
9
10
|
|
|
10
11
|
module.exports = class DataTemplate extends require('../../plugin') {
|
|
11
12
|
|
|
@@ -92,32 +93,6 @@ module.exports = class DataTemplate extends require('../../plugin') {
|
|
|
92
93
|
}
|
|
93
94
|
module.exports.asJson = asJson
|
|
94
95
|
module.exports.asCsv = asCsv
|
|
95
|
-
module.exports.filterStringAsRegex = filterStringAsRegex
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Turns a filter string with optional meta characters into a regular expression.
|
|
99
|
-
* If no meta characters are found, the string is treated as an include pattern.
|
|
100
|
-
*
|
|
101
|
-
* @param {string} spec the filter string
|
|
102
|
-
* @returns {RegExp} the regular expression
|
|
103
|
-
*/
|
|
104
|
-
function filterStringAsRegex(spec) {
|
|
105
|
-
if (typeof spec === 'string') {
|
|
106
|
-
try {
|
|
107
|
-
if (spec.match(/[\^$|*]/)) {
|
|
108
|
-
spec = spec.replace(/^\*/, '.*') // * at start would be invalid, so make it a .*
|
|
109
|
-
return new RegExp(spec, 'i')
|
|
110
|
-
}
|
|
111
|
-
else { // no meta chars -> include semantics
|
|
112
|
-
spec = spec.replace(/\./g, '\\.') // escape literal dot
|
|
113
|
-
return new RegExp(spec, 'i')
|
|
114
|
-
}
|
|
115
|
-
} catch (err) {
|
|
116
|
-
throw err.message // user error, so cut off stack trace
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return /.*/
|
|
120
|
-
}
|
|
121
96
|
|
|
122
97
|
function parseQueryStr(queryStr) {
|
|
123
98
|
const params = new URLSearchParams(queryStr) // e.g. key1&key2=false
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const { readProject } = require('../../projectReader')
|
|
2
|
+
const { merge } = require('../../merge')
|
|
3
|
+
|
|
4
|
+
module.exports = class EsmTemplate extends require('../../plugin') {
|
|
5
|
+
|
|
6
|
+
static help() {
|
|
7
|
+
return 'ESM-compatible Node.js project'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async run() {
|
|
11
|
+
const project = readProject()
|
|
12
|
+
const { configFile } = project
|
|
13
|
+
await merge(__dirname, 'files/package.json.hbs').into(configFile, { with: project, forceOverwrite: true })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{{! ESM }}
|
|
2
|
+
{{#hasEsmStyle}}
|
|
3
|
+
import cds from '@sap/cds'
|
|
4
|
+
{{#hasTyper}}
|
|
5
|
+
{{#allNames}}
|
|
6
|
+
import { {{allNames}} } from '#cds-models/{{{servicePath}}}'
|
|
7
|
+
{{/allNames}}
|
|
8
|
+
{{/hasTyper}}
|
|
9
|
+
|
|
10
|
+
{{! class declaration }}
|
|
11
|
+
export class {{serviceClass}} extends cds.ApplicationService { init() {
|
|
12
|
+
{{/hasEsmStyle}}
|
|
13
|
+
{{! CJS }}
|
|
14
|
+
{{^hasEsmStyle}}
|
|
15
|
+
const cds = require('@sap/cds')
|
|
16
|
+
{{#hasTyper}}
|
|
17
|
+
{{#allNames}}
|
|
18
|
+
const { {{allNames}} } = require('#cds-models/{{{servicePath}}}')
|
|
19
|
+
{{/allNames}}
|
|
20
|
+
{{/hasTyper}}
|
|
21
|
+
|
|
22
|
+
{{! class declaration }}
|
|
23
|
+
module.exports = class {{serviceClass}} extends cds.ApplicationService { init() {
|
|
24
|
+
{{/hasEsmStyle}}
|
|
25
|
+
{{! dynamic model imports }}
|
|
26
|
+
{{^hasTyper}}
|
|
27
|
+
|
|
28
|
+
{{#entityNames}}
|
|
29
|
+
const { {{entityNames}} } = cds.entities('{{serviceName}}')
|
|
30
|
+
{{/entityNames}}
|
|
31
|
+
{{/hasTyper}}
|
|
32
|
+
|
|
33
|
+
{{! entity handlers }}
|
|
34
|
+
{{#entities}}
|
|
35
|
+
this.before (['CREATE', 'UPDATE'], {{name}}, async (req) => {
|
|
36
|
+
console.log('Before CREATE/UPDATE {{name}}', req.data)
|
|
37
|
+
})
|
|
38
|
+
this.after ('READ', {{name}}, async ({{dataVar}}, req) => {
|
|
39
|
+
console.log('After READ {{name}}', {{dataVar}})
|
|
40
|
+
})
|
|
41
|
+
{{/entities}}
|
|
42
|
+
|
|
43
|
+
{{! action handlers }}
|
|
44
|
+
{{#operations}}
|
|
45
|
+
{{#hasTyper}}
|
|
46
|
+
this.on ({{name}}, async (req) => {
|
|
47
|
+
{{/hasTyper}}
|
|
48
|
+
{{^hasTyper}}
|
|
49
|
+
this.on ('{{name}}', async (req) => {
|
|
50
|
+
{{/hasTyper}}
|
|
51
|
+
console.log('On {{name}}', req.data)
|
|
52
|
+
})
|
|
53
|
+
{{/operations}}
|
|
54
|
+
|
|
55
|
+
return super.init()
|
|
56
|
+
}}
|
|
@@ -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
|
}
|
|
@@ -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
|
}
|