@sap/cds-dk 8.4.1 → 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 +43 -2
- package/bin/add.js +2 -12
- package/bin/cds.js +5 -0
- package/bin/debug.js +90 -0
- package/bin/watch.js +12 -2
- package/lib/bind/index.js +1 -1
- 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/index.js +14 -14
- 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 +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-csv.js +1 -1
- package/lib/init/template/data/as-json.js +5 -5
- 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/hana/files/package.json.hbs +18 -4
- 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 +108 -39
- 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/side-by-side-extensibility/index.js +1 -0
- 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 +28 -33
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
2
|
const { join, resolve, basename } = require('path')
|
|
3
|
-
const { fs, exists, isdir, path } = cds.utils
|
|
3
|
+
const { fs, exists, isfile, isdir, path } = cds.utils
|
|
4
4
|
const { parseXml } = require('./xml')
|
|
5
5
|
const { REGEX_JAVA_VERSION, JAVA_LTS_VERSIONS } = require('./constants')
|
|
6
6
|
const TRACE = cds.debug('trace')
|
|
@@ -12,13 +12,15 @@ module.exports = new class ProjectReader {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
#setPackageInfo() {
|
|
15
|
+
this.isJava = (exists('pom.xml') || cds.cli.options?.add?.has('java'))
|
|
16
|
+
&& !cds.env.for('cds').profiles.includes('node') // opt-out for hybrid projects like sflight
|
|
17
|
+
|
|
15
18
|
// 1. Use project name and static default values
|
|
16
|
-
this.appVersion =
|
|
19
|
+
this.appVersion = this.isJava ? '1.0.0-SNAPSHOT' : '1.0.0'
|
|
17
20
|
this.appName = this.appId = basename(cds.root)
|
|
18
21
|
this.appDescription = 'A simple CAP project.'
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
if (isJava) {
|
|
23
|
+
if (this.isJava) {
|
|
22
24
|
let artifactId, description, version, properties
|
|
23
25
|
try {
|
|
24
26
|
({ artifactId, description, version, properties } = parseXml(resolve(cds.root, 'pom.xml')) ?? {})
|
|
@@ -74,6 +76,10 @@ module.exports = new class ProjectReader {
|
|
|
74
76
|
* @property {boolean} isUI5 Indicates if the project is a UI5 project.
|
|
75
77
|
* @property {boolean} isJava Indicates if the project is using Java.
|
|
76
78
|
* @property {boolean} isNodejs Indicates if the project is using Node.js.
|
|
79
|
+
* @property {boolean} isESM Indicates if the project is an ESM project (type 'module')
|
|
80
|
+
* @property {boolean} hasEsmStyle Indicates if code supports ESM-style imports and exports. This is true for ESM and Typescript projects.
|
|
81
|
+
* @property {boolean} isTypescript Indicates if the project is a Typescript project.
|
|
82
|
+
* @property {boolean} hasTyper Indicates if code supports CDS typer.
|
|
77
83
|
* @property {boolean} hasHelmUnifiedRuntime Indicates if the project uses Unified Runtime Helm deployment.
|
|
78
84
|
* @property {boolean} hasHelm Indicates if the project uses CAP Helm deployment.
|
|
79
85
|
* @property {boolean} hasMta Indicates if the project uses MTA deployment.
|
|
@@ -95,7 +101,7 @@ module.exports = new class ProjectReader {
|
|
|
95
101
|
* @property {boolean} hasMTXRouteJava Indicates if the approuter should have a multitenant subscription route (java).
|
|
96
102
|
* @property {boolean} hasMalwareScanner Indicates if the project uses the SAP Malware Scanning service.
|
|
97
103
|
* @property {boolean} hasDynatrace Indicates if the project uses Dynatrace.
|
|
98
|
-
*
|
|
104
|
+
* @property {boolean} hasCloudLogging Indicates if the project uses Cloud Logging.
|
|
99
105
|
*/
|
|
100
106
|
|
|
101
107
|
/**
|
|
@@ -110,18 +116,27 @@ module.exports = new class ProjectReader {
|
|
|
110
116
|
const { appVersion, appName, appId, appDescription, jdkVersion } = this
|
|
111
117
|
TRACE?.({ env })
|
|
112
118
|
|
|
119
|
+
const pkgJson = () => exists('package.json') ? JSON.parse(fs.readFileSync(join(cds.root, 'package.json'))) : {}
|
|
120
|
+
const _isESM = () => pkgJson().type === 'module'
|
|
121
|
+
const _isTypescript = () => !!pkgJson().dependencies?.typescript || !!pkgJson().devDependencies?.typescript
|
|
122
|
+
const _hasTyper = () => !!pkgJson().dependencies?.['@cap-js/cds-typer'] || !!pkgJson().devDependencies?.['@cap-js/cds-typer']
|
|
123
|
+
|
|
113
124
|
const _inProd = plugin => require(`./template/${plugin}`).hasInProduction(env)
|
|
114
|
-
const _ui5 = () =>
|
|
125
|
+
const _ui5 = () => pkgJson().sapux
|
|
115
126
|
const _uiEmbedded = () => exists(join(env.folders.app, 'xs-app.json'))
|
|
116
127
|
const _uiModule = () => !_uiEmbedded() && isdir(env.folders.app) && fs.readdirSync(resolve(cds.root, env.folders.app)).length > 0
|
|
117
|
-
const _isJava = () =>
|
|
128
|
+
const _isJava = () => this.isJava
|
|
118
129
|
const isApp = name => name !== 'appconfig' && !name.startsWith('_') && name !== 'router' && name !== 'portal'
|
|
119
130
|
const reserved = {
|
|
120
131
|
has: x => _inProd(x),
|
|
121
132
|
profile: () => cds.cli.options?.for,
|
|
122
|
-
language: () =>
|
|
133
|
+
language: () => _isJava() ? 'java' : 'nodejs',
|
|
123
134
|
isJava: () => _isJava(),
|
|
124
135
|
isNodejs: () => !_isJava(),
|
|
136
|
+
isTypescript: () => _isTypescript(),
|
|
137
|
+
isESM: () => _isESM,
|
|
138
|
+
hasEsmStyle: () => _isTypescript() || _isESM(),
|
|
139
|
+
hasTyper: () => _hasTyper(),
|
|
125
140
|
srvPath: () => join(env.build.target, env.folders.srv.replace(/\/$/, '')).replace(/\\/g, '/'),
|
|
126
141
|
archiveName: () => {
|
|
127
142
|
const pomXmlPath = resolve(cds.root, env.folders.srv, 'pom.xml')
|
|
@@ -135,7 +150,7 @@ module.exports = new class ProjectReader {
|
|
|
135
150
|
const path = join(env.build.target, name).replace(/\\/g, '/')
|
|
136
151
|
return { folder, name, path }
|
|
137
152
|
},
|
|
138
|
-
configFile: () =>
|
|
153
|
+
configFile: () => _isJava() ? '.cdsrc.json' : 'package.json',
|
|
139
154
|
apps: () => {
|
|
140
155
|
const apps = fs.readdirSync(path.resolve(cds.root, cds.env.folders.app)).filter(e =>
|
|
141
156
|
isdir(path.join(cds.root, cds.env.folders.app, e)) && isApp(e)
|
|
@@ -191,3 +206,54 @@ module.exports = new class ProjectReader {
|
|
|
191
206
|
return project
|
|
192
207
|
}
|
|
193
208
|
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Finds and reads SpringBoot configuration in `cds.root/srv` dir.
|
|
212
|
+
* This is done in a best-effort way. In no way can we replciate all of SpringBoot's
|
|
213
|
+
* config variants.
|
|
214
|
+
* @param {string[]?} roots array of directories to search for application.yaml, like `srv`
|
|
215
|
+
* @returns {Promise<object[]>} array of objects with `cds` property
|
|
216
|
+
*/
|
|
217
|
+
module.exports._readSpringBootConfig = async function (roots=[cds.env.folders.srv]) {
|
|
218
|
+
const configNodes = []
|
|
219
|
+
const rootDirs = roots.map(d => join(cds.root, d))
|
|
220
|
+
for (let r of rootDirs) {
|
|
221
|
+
const file = isfile (join (r,'./src/main/resources/application.yaml'))
|
|
222
|
+
if (file) {
|
|
223
|
+
const yaml = cds.load.yaml(file)
|
|
224
|
+
for (let yamlDoc of Array.isArray(yaml) ? yaml : [yaml]) {
|
|
225
|
+
let cds = yamlDoc?.cds;
|
|
226
|
+
if (!cds) continue
|
|
227
|
+
cds = _normalizeSpringBootCfg(cds)
|
|
228
|
+
configNodes.push({cds})
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return configNodes
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Normalize SpringBoot's dots in keys to express nested objects
|
|
237
|
+
*
|
|
238
|
+
* @example: `cds.foo.bar = 1` --> `cds: { foo: { bar: 1 } }`
|
|
239
|
+
* @param {object} obj
|
|
240
|
+
* @returns {object}
|
|
241
|
+
*/
|
|
242
|
+
function _normalizeSpringBootCfg(obj) {
|
|
243
|
+
if (typeof obj !== 'object') return obj
|
|
244
|
+
Object.keys(obj).forEach(k => {
|
|
245
|
+
const prop = k.split('.')
|
|
246
|
+
const last = prop.pop()
|
|
247
|
+
// and define the object if not already defined
|
|
248
|
+
const res = prop.reduce((o, key) => {
|
|
249
|
+
// define the object if not defined and return
|
|
250
|
+
return o[key] = o[key] ?? {}
|
|
251
|
+
}, obj)
|
|
252
|
+
res[last] = obj[k]
|
|
253
|
+
// recursively normalize
|
|
254
|
+
_normalizeSpringBootCfg(obj[k])
|
|
255
|
+
// delete the original property from object if it was rewritten
|
|
256
|
+
if (prop.length) delete obj[k]
|
|
257
|
+
})
|
|
258
|
+
return obj
|
|
259
|
+
}
|
|
@@ -1,18 +1,7 @@
|
|
|
1
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
2
|
"cds": {
|
|
10
3
|
"requires": {
|
|
11
|
-
"
|
|
12
|
-
"telemetry": {
|
|
13
|
-
"kind": "to-cloud-logging"
|
|
14
|
-
}
|
|
15
|
-
}
|
|
4
|
+
"cloud-logging": true
|
|
16
5
|
}
|
|
17
6
|
}
|
|
18
7
|
}
|
|
@@ -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
|
+
|
|
@@ -39,7 +39,7 @@ module.exports = async (data={}, csn, { headerOnly=false, separator=',' }={}) =>
|
|
|
39
39
|
}
|
|
40
40
|
record.push(...Object.values(value))
|
|
41
41
|
}
|
|
42
|
-
else if (element.is_struct) {
|
|
42
|
+
else if (element.is_struct && element.type !== 'cds.Map') {
|
|
43
43
|
if (i === 0) {
|
|
44
44
|
header.push(...Object.keys(value).map(structField => name + '_' + structField))
|
|
45
45
|
}
|
|
@@ -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
|
})
|
|
@@ -229,9 +229,11 @@ function dataForField(element, artifact, csn, record, data, parent, depth, refer
|
|
|
229
229
|
}
|
|
230
230
|
case 'cds.hana.REAL':
|
|
231
231
|
case 'cds.Decimal': {
|
|
232
|
-
const
|
|
232
|
+
const prec = element.precision ? Math.min(Math.pow(10, element.precision), Number.MAX_SAFE_INTEGER) : 100
|
|
233
|
+
const range = { from: 0, to: prec }
|
|
234
|
+
const scale = element.scale ?? (element.precision ? 0 : 2) // scale is 0 if only precision is given
|
|
233
235
|
annotatedIntRange(element, range)
|
|
234
|
-
return +(randomDecimalInRange(range).toFixed(
|
|
236
|
+
return +(randomDecimalInRange(range).toFixed(scale))
|
|
235
237
|
}
|
|
236
238
|
case 'cds.Double': {
|
|
237
239
|
const range = {from: 0, to: 100}
|
|
@@ -309,8 +311,6 @@ function dataForField(element, artifact, csn, record, data, parent, depth, refer
|
|
|
309
311
|
return
|
|
310
312
|
case 'cds.Vector': // TODO add Vector
|
|
311
313
|
return
|
|
312
|
-
case 'cds.Map':
|
|
313
|
-
return {}
|
|
314
314
|
default: {
|
|
315
315
|
console.error(`Unknown type ${element._type} (${element.type}) in '${artifact.name}:${element.name}'`)
|
|
316
316
|
return
|
|
@@ -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
|
+
}
|
|
@@ -3,10 +3,7 @@
|
|
|
3
3
|
"dependencies": {
|
|
4
4
|
"@cap-js/hana": "^1"
|
|
5
5
|
},
|
|
6
|
-
{{/isNodejs}}
|
|
7
|
-
{{#isNodejs}}
|
|
8
6
|
"cds": {
|
|
9
|
-
{{/isNodejs}}
|
|
10
7
|
"sql": {
|
|
11
8
|
"native_hana_associations": false
|
|
12
9
|
}{{#profile}},
|
|
@@ -22,7 +19,24 @@
|
|
|
22
19
|
{{/shortcut}}
|
|
23
20
|
}
|
|
24
21
|
}{{/profile}}
|
|
25
|
-
{{#isNodejs}}
|
|
26
22
|
}
|
|
27
23
|
{{/isNodejs}}
|
|
24
|
+
{{#isJava}}
|
|
25
|
+
"requires": {
|
|
26
|
+
{{#profile}}
|
|
27
|
+
"[{{profile}}]": {
|
|
28
|
+
{{/profile}}
|
|
29
|
+
{{#shortcut}}
|
|
30
|
+
"db": "hana"
|
|
31
|
+
{{/shortcut}}
|
|
32
|
+
{{^shortcut}}
|
|
33
|
+
"db": {
|
|
34
|
+
"kind": "hana"
|
|
35
|
+
}
|
|
36
|
+
{{/shortcut}}
|
|
37
|
+
{{#profile}}
|
|
38
|
+
}
|
|
39
|
+
{{/profile}}
|
|
40
|
+
}
|
|
41
|
+
{{/isJava}}
|
|
28
42
|
}
|
|
@@ -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
|
}
|