@sap/cds-dk 8.0.3 → 8.1.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/bind.js +3 -3
- package/bin/cds.js +11 -15
- package/bin/init.js +3 -6
- package/bin/version.js +5 -3
- package/lib/build/provider/hana/index.js +5 -6
- package/lib/build/provider/mtx-sidecar/index.js +10 -3
- package/lib/compile/to-mermaid.js +27 -12
- package/lib/deploy/to-hana/hana.js +10 -8
- package/lib/deploy/to-hana/hdiDeployUtil.js +9 -1
- package/lib/init/constants.js +9 -3
- package/lib/init/index.js +6 -4
- package/lib/init/projectReader.js +3 -3
- package/lib/init/template/audit-logging/files/package.sidecar.json +7 -0
- package/lib/init/template/audit-logging/index.js +1 -1
- package/lib/init/template/cf-manifest/index.js +4 -0
- package/lib/init/template/cov2ap/files/dependencies.package.json +5 -0
- package/lib/init/template/cov2ap/index.js +31 -0
- package/lib/init/template/event-queue/files/dependencies.package.json +5 -0
- package/lib/init/template/event-queue/index.js +35 -0
- package/lib/init/template/http/index.js +37 -28
- package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +0 -1
- package/lib/init/template/sample/files/app/browse/webapp/manifest.json +0 -1
- package/lib/init/template/sample/files/nodejs/admin-service.js +13 -8
- package/lib/init/template/sample/files/nodejs/cat-service.js +27 -15
- package/lib/init/template/typescript/files/package.json +3 -3
- package/lib/init/template/websocket/files/dependencies.package.json +5 -0
- package/lib/init/template/websocket/index.js +35 -0
- package/lib/init/template/workzone/index.js +0 -3
- package/npm-shrinkwrap.json +14 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,33 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
9
9
|
|
|
10
|
+
## Version 8.1.0 - 2024-07-26
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `cds compile --to mermaid` can be configured with a layout direction like `LR`.
|
|
15
|
+
- `cds deploy --to hana` now shows the `hdi-deploy` version when running.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Better generation of http files with `cds add http`, especially for draft requests.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- `cds add` recognizes `hasCfManifest` as inactive unless a `manifest.yml` file exists.
|
|
24
|
+
- `cds add` help and suggestions are only shown when the `help` method is implemented.
|
|
25
|
+
- `cds add sample` does not include the deprecated `synchronizationMode` property in `manifest.json` files any more.
|
|
26
|
+
- `cds compile --to mermaid` no longer produces empty `namespace` blocks that would lead to rendering errors.
|
|
27
|
+
- `cds compile --to mermaid` no longer fails for complex queries like joins.
|
|
28
|
+
- `cds build --for mtx-sidecar` now correctly supports different `srv` folder name.
|
|
29
|
+
- `cds add audit-log` in combination with `cds add multitenancy` correctly adds the audit-log dependency to the MTX sidecar.
|
|
30
|
+
- `cds build` adds the correct hdbtabledata file paths to the hana result set.
|
|
31
|
+
- `cds add` plugins can now correctly interpret `cds.cli` properties.
|
|
32
|
+
- `cds deploy --to hana --dry` now shows correct files with their content.
|
|
33
|
+
- `cds version` now reports the correct version of `@sap/cds` if this one is installed locally along with a local `@sap/cds-dk` installation.
|
|
34
|
+
- Shell completion now works correctly for `cds init --add`.
|
|
35
|
+
- `cds build --for mtx-sidecar` no longer fails if the severity of a compilation error has been downgraded from `Error` to `Warning`.
|
|
36
|
+
|
|
10
37
|
## Version 8.0.3 - 2024-07-20
|
|
11
38
|
|
|
12
39
|
### Fixed
|
|
@@ -142,7 +169,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
142
169
|
|
|
143
170
|
- `cds add notebook` is removed. CAP Jupyter Notebooks have been replaced by custom CAP Notebooks for VS Code which are now part of the CDS Editor. See https://cap.cloud.sap/docs/tools/#add-cds-editor for more.
|
|
144
171
|
|
|
145
|
-
|
|
146
172
|
## Version 7.9.6 - 2024-07-19
|
|
147
173
|
|
|
148
174
|
### Fixed
|
package/bin/bind.js
CHANGED
|
@@ -46,7 +46,7 @@ module.exports = Object.assign(cds_bind, {
|
|
|
46
46
|
|
|
47
47
|
*-a | --to-app-services* <app>
|
|
48
48
|
|
|
49
|
-
Bind
|
|
49
|
+
Bind services of a given application (Cloud Foundry only).
|
|
50
50
|
|
|
51
51
|
*--no-create-service-key*
|
|
52
52
|
|
|
@@ -164,11 +164,11 @@ async function cds_bind(args, options) {
|
|
|
164
164
|
options.targets = options.to.split(/,/g);
|
|
165
165
|
|
|
166
166
|
if (options.targets.length >= 2 && options.serviceArg) {
|
|
167
|
-
throw `Service argument cannot be specified together with multiple
|
|
167
|
+
throw `Service argument cannot be specified together with multiple target (${term.bold('--to')}) services. Use one service per call or omit the service argument.`;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
if (options.targets.length >= 2 && options.kind) {
|
|
171
|
-
throw `The option '--kind' cannot be specified together with multiple
|
|
171
|
+
throw `The option '--kind' cannot be specified together with multiple target (${term.bold('--to')}) services. Use one service per call or omit the ${term.bold('--kind')} option.`;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
const services = await bind({ ...options, out });
|
package/bin/cds.js
CHANGED
|
@@ -55,8 +55,15 @@ const cli = {
|
|
|
55
55
|
argv = argv.slice(0, endOfOptions)
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
// REVISIT: Have to load plugins to allow for plugin-contributed flags!
|
|
59
|
+
if (cmd === 'add') {
|
|
60
|
+
cds.cli = { command: 'add' }
|
|
61
|
+
await cds.plugins
|
|
62
|
+
for (const [, Plugin] of cds.add.registered) {
|
|
63
|
+
task.options.push(...Object.keys((new Plugin).options()).map(key => '--'+key))
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const args = this.args(task, argv)
|
|
60
67
|
args[0].push(...appendArgs)
|
|
61
68
|
cds.cli = {
|
|
62
69
|
command: cmd,
|
|
@@ -64,17 +71,6 @@ const cli = {
|
|
|
64
71
|
options: args[1],
|
|
65
72
|
}
|
|
66
73
|
|
|
67
|
-
if (cmd === 'add') { // cds add plugins allow additional custom options
|
|
68
|
-
await cds.plugins
|
|
69
|
-
for (const [, Plugin] of cds.add.registered) {
|
|
70
|
-
const options = Object.keys((new Plugin).options()).map(key => '--'+key)
|
|
71
|
-
task.options.push(options)
|
|
72
|
-
options.forEach(o => unknown.delete(o))
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
if (unknown.size === 1) throw 'Invalid option: ' + Array.from(unknown)[0]
|
|
76
|
-
if (unknown.size > 1) throw 'Invalid options: ' + Array.from(unknown).join(', ')
|
|
77
|
-
|
|
78
74
|
if (args[1]?.['resolve-bindings']) await _resolveBindings({ silent: cmd === 'env' })
|
|
79
75
|
|
|
80
76
|
// cds.root may for some subcommands not point to the correct directory (e.g. cds w where users can pass a project directory)
|
|
@@ -106,7 +102,7 @@ const cli = {
|
|
|
106
102
|
},
|
|
107
103
|
|
|
108
104
|
// TODO replace w/ common arg parser from node
|
|
109
|
-
args (task, argv
|
|
105
|
+
args (task, argv) {
|
|
110
106
|
|
|
111
107
|
const { options:o=[], flags:f=[], shortcuts:s=[] } = task
|
|
112
108
|
const _global = /^--(profile|production|sql|odata|add-.*|build-.*|cdsc-.*|odata-.*|folders-.*)$/
|
|
@@ -120,7 +116,7 @@ const cli = {
|
|
|
120
116
|
else if ((k = o.indexOf(a)) >= 0) add(o[k],argv[++i])
|
|
121
117
|
else if ((k = f.indexOf(a)) >= 0) add(f[k])
|
|
122
118
|
else if (_global.test(a)) add_global(a, _flags[a] || argv[++i])
|
|
123
|
-
else
|
|
119
|
+
else throw 'Invalid option: '+ a
|
|
124
120
|
}
|
|
125
121
|
// consistent production setting for NODE_ENV and CDS_ENV
|
|
126
122
|
// if (process.env.NODE_ENV !== 'production') process.env.NODE_ENV = process.env.CDS_ENV?.split(',').find(p => p === 'production') || process.env.NODE_ENV
|
package/bin/init.js
CHANGED
|
@@ -61,18 +61,15 @@ async function handleCompletion(currentWord, previousWord, argv, util) {
|
|
|
61
61
|
return allOptionsFlags;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
if (previousWord === '--add') {
|
|
65
|
-
return util.completionFs.readTemplates(argv);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
64
|
if (previousWord === '--java:package') {
|
|
69
65
|
const excludes = ['_merging'];
|
|
70
66
|
return (await util.completionFs.readdir(currentWord, { files: false }))
|
|
71
67
|
.filter(e => !excludes.includes(e));
|
|
72
68
|
}
|
|
73
69
|
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
const templates = await util.completionFs.readTemplates(argv);
|
|
71
|
+
if (previousWord === '--add' || argv.includes('--add') && previousWord.endsWith(',')) {
|
|
72
|
+
return templates;
|
|
76
73
|
}
|
|
77
74
|
|
|
78
75
|
return allOptionsFlags;
|
package/bin/version.js
CHANGED
|
@@ -61,7 +61,8 @@ function list_versions(args, options) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
function info({ root:cwd=process.cwd() }) {
|
|
64
|
-
const
|
|
64
|
+
const cds = require('../lib')
|
|
65
|
+
const env = cds.env.for('cds', cwd)
|
|
65
66
|
env.cli ??= {}; env.cli.version ??= {}
|
|
66
67
|
const javaInfo = !env.cli.version.skipjava && env['project-nature'] === 'java'
|
|
67
68
|
if (javaInfo && process.stderr.isTTY) {
|
|
@@ -74,10 +75,11 @@ function info({ root:cwd=process.cwd() }) {
|
|
|
74
75
|
..._versions_from(_pkg_path('@sap/cds-dk', npmRoot), '@sap/cds-dk (global)'), // ... or installed globally
|
|
75
76
|
..._versions_from(_pkg_path('@sap/cds-dk', cwd)), // local dk, important for Java projects
|
|
76
77
|
..._versions_from(_pkg_path('@sap/cds', __dirname)), // @sap/cds from here
|
|
77
|
-
..._versions_from(
|
|
78
|
+
..._versions_from(_pkg_path('@sap/cds', cds.home)), // ... or local @sap/cds
|
|
79
|
+
..._versions_from(cwd), // ... more from local app dependencies
|
|
78
80
|
..._versions_from(_pkg_path('@sap/cds-compiler', cwd, __dirname)), // compiler is no direct dependency, so list it explicitly
|
|
79
81
|
'Node.js': process.version,
|
|
80
|
-
'home':
|
|
82
|
+
'home': cds.home, // effective cds home
|
|
81
83
|
}
|
|
82
84
|
// sort Node entries
|
|
83
85
|
versions = Object.keys(versions).sort().reduce((all, key) => { all[key] = versions[key]; return all }, {})
|
|
@@ -26,7 +26,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
26
26
|
if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
|
|
27
27
|
this._result = {
|
|
28
28
|
dest: this.task.dest,
|
|
29
|
-
hana:
|
|
29
|
+
hana: new Set()
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
this.task.options.compileDest = path.resolve(this.task.dest, this.task.options.compileDest || DEFAULT_COMPILE_DEST_FOLDER)
|
|
@@ -228,7 +228,6 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
const promises = []
|
|
231
|
-
const relDest = path.relative(this.task.dest, this.task.options.compileDest)
|
|
232
231
|
const options = { ...this.options(), messages: this.messages, dirs: csvDirs, baseDir: this.task.options.compileDest }
|
|
233
232
|
|
|
234
233
|
const toHdbtabledata = cds.compile.to.hdbtabledata ?? require(path.join(cds.home, 'bin/build/provider/hana/2tabledata')) // cds@6 compatibility
|
|
@@ -240,7 +239,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
240
239
|
let tableDataPath = csvFolder.startsWith(this.task.options.compileDest) ? csvFolder : this.task.options.compileDest
|
|
241
240
|
tableDataPath = path.join(tableDataPath, file)
|
|
242
241
|
if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
|
|
243
|
-
this._result.hana.
|
|
242
|
+
this._result.hana.add(path.relative(this.task.dest, tableDataPath))
|
|
244
243
|
}
|
|
245
244
|
promises.push(this.write(tableData).to(tableDataPath))
|
|
246
245
|
}
|
|
@@ -296,7 +295,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
296
295
|
|
|
297
296
|
hdiPlugins.add(suffix)
|
|
298
297
|
if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
|
|
299
|
-
this._result.hana.
|
|
298
|
+
this._result.hana.add(path.join(relDest, file))
|
|
300
299
|
}
|
|
301
300
|
promises.push(this.write(content).to(path.join(this.task.options.compileDest, file)))
|
|
302
301
|
if (suffix === FILE_EXT_HDBTABLE) {
|
|
@@ -345,7 +344,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
345
344
|
migrationTableFiles.push(path.join(relDbSrcDir, fileName))
|
|
346
345
|
if (changed) {
|
|
347
346
|
if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
|
|
348
|
-
this._result.hana.
|
|
347
|
+
this._result.hana.add(path.join("src", fileName))
|
|
349
348
|
}
|
|
350
349
|
promises.push(this.write(content).to(path.join(dbSrcDir, fileName)))
|
|
351
350
|
} else {
|
|
@@ -354,7 +353,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
354
353
|
}
|
|
355
354
|
} else {
|
|
356
355
|
if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
|
|
357
|
-
this._result.hana.
|
|
356
|
+
this._result.hana.add(path.join(relDestDir, fileName))
|
|
358
357
|
}
|
|
359
358
|
promises.push(this.write(content).to(path.join(this.task.options.compileDest, fileName)))
|
|
360
359
|
if (suffix === FILE_EXT_HDBTABLE) {
|
|
@@ -45,7 +45,7 @@ class MtxSidecarBuildPlugin extends NodejsBuildPlugin {
|
|
|
45
45
|
*/
|
|
46
46
|
async _buildNodeApp(sidecarEnv) {
|
|
47
47
|
const destSidecar = this.task.dest
|
|
48
|
-
const destSidecarSrc = path.join(destSidecar,
|
|
48
|
+
const destSidecarSrc = path.join(destSidecar, sidecarEnv.folders.srv)
|
|
49
49
|
const model = this._compileSidecarSync(sidecarEnv)
|
|
50
50
|
await this.compileToJson(model, path.join(destSidecarSrc, DEFAULT_CSN_FILE_NAME))
|
|
51
51
|
await this.collectLanguageBundles(model, path.join(destSidecarSrc, getI18nDefaultFolder()))
|
|
@@ -129,8 +129,15 @@ class MtxSidecarBuildPlugin extends NodejsBuildPlugin {
|
|
|
129
129
|
this.pushMessage(`CDS models [${unresolved.join(', ')}] required by MTX sidecar cannot be resolved. Make sure up-to-date versions of the missing npm modules are installed.`, ERROR)
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
// synchronous compilation
|
|
133
|
-
|
|
132
|
+
// using synchronous compilation as cds.root and cds.env have been switched to mtx/sidecar
|
|
133
|
+
// using different messages array ensures that cds compilation doesn't fail because of
|
|
134
|
+
// existing compilation error messages that have been downgraded to warnings
|
|
135
|
+
// see cap/issues/issues/16401#issuecomment-7205397
|
|
136
|
+
const messages = []
|
|
137
|
+
const model = cds.load(modelFilePaths, { sync: true, messages })
|
|
138
|
+
messages.forEach(msg => this.messages.push(msg))
|
|
139
|
+
|
|
140
|
+
return model
|
|
134
141
|
} finally {
|
|
135
142
|
// restore project scope
|
|
136
143
|
cds.root = root
|
|
@@ -2,12 +2,13 @@ const cds = require('../cds')
|
|
|
2
2
|
|
|
3
3
|
module.exports = function to_mermaid (model, options={}) {
|
|
4
4
|
options = Object.assign({
|
|
5
|
-
assocnames: false,
|
|
6
|
-
elements : false,
|
|
7
|
-
min : false,
|
|
8
|
-
namespaces: true,
|
|
9
|
-
queries : false,
|
|
10
|
-
service : undefined // filter service by name, available in CLI
|
|
5
|
+
assocnames: false, // include association names
|
|
6
|
+
elements : false, // include all elements { falsy | 'all'|true | 'keys' }
|
|
7
|
+
min : false, // minify the model, i.e. remove unused definitions, available in CLI
|
|
8
|
+
namespaces: true, // group entities by namespace/service
|
|
9
|
+
queries : false, // include relations for queries
|
|
10
|
+
service : undefined, // filter service by name, available in CLI
|
|
11
|
+
direction : undefined // direction of the graph
|
|
11
12
|
}, cds.env.mermaid ?? {}, options)
|
|
12
13
|
|
|
13
14
|
const elemOpts = options.elements === 'none' ? false : options.elements
|
|
@@ -27,6 +28,7 @@ module.exports = function to_mermaid (model, options={}) {
|
|
|
27
28
|
const entityFilter = e => !e.name.startsWith('cds.') && !e.name.endsWith('.texts')
|
|
28
29
|
|
|
29
30
|
let diag = ''
|
|
31
|
+
const direction = options.direction ? ` direction ${options.direction}\n` : ''
|
|
30
32
|
|
|
31
33
|
// if requested, filter by one service
|
|
32
34
|
model.all('service')
|
|
@@ -56,8 +58,10 @@ module.exports = function to_mermaid (model, options={}) {
|
|
|
56
58
|
})
|
|
57
59
|
|
|
58
60
|
// create namespace groups and classes
|
|
59
|
-
Object.keys(namespaces)
|
|
60
|
-
|
|
61
|
+
Object.keys(namespaces)
|
|
62
|
+
.filter (ns => namespaces[ns].length) // empty namespaces can cause rendering errors, so skip them
|
|
63
|
+
.forEach(ns => {
|
|
64
|
+
if (!diag.length) diag = `classDiagram\n${direction}`
|
|
61
65
|
diag += ` namespace ${ns.replaceAll('.','_')} {\n`
|
|
62
66
|
namespaces[ns].forEach (e => {
|
|
63
67
|
const simpleName = e.name.slice(e.name.lastIndexOf('.')+1)
|
|
@@ -65,7 +69,7 @@ module.exports = function to_mermaid (model, options={}) {
|
|
|
65
69
|
diag += `["${simpleName}"]` // simple name as label
|
|
66
70
|
if (elemOpts) {
|
|
67
71
|
diag += ` {\n`
|
|
68
|
-
e.elements
|
|
72
|
+
e.elements?.forEach (el => {
|
|
69
73
|
if ((elemOpts === 'all' || elemOpts === true) || (el.key && elemOpts == 'keys')) {
|
|
70
74
|
diag += ` +${el._type?.replace('cds.','')} ${el.name}`
|
|
71
75
|
diag += `\n`
|
|
@@ -85,8 +89,8 @@ module.exports = function to_mermaid (model, options={}) {
|
|
|
85
89
|
? Array.from(seenEntities).map(name => model.definitions[name])
|
|
86
90
|
: model.all('entity').filter (entityFilter) // if no grouping and no service filter is on
|
|
87
91
|
entities.forEach(e => {
|
|
88
|
-
if (!diag.length) diag = `classDiagram\n`
|
|
89
|
-
e.elements
|
|
92
|
+
if (!diag.length) diag = `classDiagram\n${direction}`
|
|
93
|
+
e.elements?.forEach (el => {
|
|
90
94
|
if (el instanceof cds.Association) {
|
|
91
95
|
if (el.target.endsWith('.texts')) return
|
|
92
96
|
const type = el.type === 'cds.Composition' ? '*-->' : '-->'
|
|
@@ -100,9 +104,20 @@ module.exports = function to_mermaid (model, options={}) {
|
|
|
100
104
|
})
|
|
101
105
|
// add relations for queries
|
|
102
106
|
if (e.query && options.queries) {
|
|
103
|
-
|
|
107
|
+
resolveTargets(e.query).forEach(t => {
|
|
108
|
+
diag += ` \`${e.name}\` ..> \`${t}\`\n`
|
|
109
|
+
})
|
|
104
110
|
}
|
|
105
111
|
})
|
|
106
112
|
|
|
107
113
|
return diag.trim()
|
|
108
114
|
}
|
|
115
|
+
|
|
116
|
+
function resolveTargets (query) {
|
|
117
|
+
if (query.target) return [ query.target.name ] // simple query, like projection on
|
|
118
|
+
|
|
119
|
+
// SELECT: { from: { args: [{ ref: ['t1'] }, { ref: ['t2'] }], join: ..., on: ... } }
|
|
120
|
+
const targets = query.SELECT?.from?.args?.filter(arg => arg.ref?.length).map (arg => arg.ref[0])
|
|
121
|
+
|
|
122
|
+
return targets
|
|
123
|
+
}
|
|
@@ -6,7 +6,6 @@ const cds = require('../../../lib/cds');
|
|
|
6
6
|
const { exists, read } = cds.utils;
|
|
7
7
|
|
|
8
8
|
const { build } = require('../../build');
|
|
9
|
-
const buildConstants = require('../../build/constants');
|
|
10
9
|
|
|
11
10
|
const hdi = require('./hdiDeployUtil');
|
|
12
11
|
const cfUtil = require('../../util/cf');
|
|
@@ -16,6 +15,7 @@ const mtaUtil = require('./mtaUtil');
|
|
|
16
15
|
const { bold, info, warn } = require('../../util/term');
|
|
17
16
|
|
|
18
17
|
const { bind, storeBindings } = require('../../bind');
|
|
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');
|
|
21
21
|
|
|
@@ -73,13 +73,13 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
|
|
|
73
73
|
vcapEnv.TARGET_CONTAINER = process.env.TARGET_CONTAINER;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
for (const { task } of buildResults) {
|
|
76
|
+
for (const { task, result } of buildResults) {
|
|
77
77
|
if (dry) {
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
console.log(`-- ${
|
|
82
|
-
console.log(
|
|
78
|
+
const sortedResult = Array.from(result.hana).sort();
|
|
79
|
+
for (const file of sortedResult) {
|
|
80
|
+
const srcPath = path.join(result.dest, file);
|
|
81
|
+
console.log(`-- ${path.relative(cds.root, srcPath)}`);
|
|
82
|
+
console.log(await read(srcPath));
|
|
83
83
|
console.log();
|
|
84
84
|
}
|
|
85
85
|
} else {
|
|
@@ -221,7 +221,9 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
|
|
|
221
221
|
|
|
222
222
|
async _build() {
|
|
223
223
|
const buildTaskOptions = {
|
|
224
|
-
for:
|
|
224
|
+
for: BUILD_TASK_HANA,
|
|
225
|
+
src: cds.env.folders.db,
|
|
226
|
+
[OUTPUT_MODE]: OUTPUT_MODE_FILESYSTEM | OUTPUT_MODE_RESULT
|
|
225
227
|
}
|
|
226
228
|
|
|
227
229
|
console.log(`Running build`);
|
|
@@ -59,7 +59,15 @@ module.exports = new class HdiDeployUtil {
|
|
|
59
59
|
Add it either to ${bold('devDependencies')} using 'npm install -D ${HDI_DEPLOY_LIB}' or install it globally using 'npm install -g ${HDI_DEPLOY_LIB}'.`);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
console.log(`
|
|
62
|
+
console.log(`HDI deployer path: ${libPath}`)
|
|
63
|
+
|
|
64
|
+
const libRoot = path.dirname(libPath);
|
|
65
|
+
try {
|
|
66
|
+
const libPackageJson = require(path.join(libRoot, 'package.json'));
|
|
67
|
+
console.log(`HDI deployer version: ${libPackageJson.version}`);
|
|
68
|
+
} catch {
|
|
69
|
+
// ignore
|
|
70
|
+
}
|
|
63
71
|
|
|
64
72
|
// let any error go through and abort deploy
|
|
65
73
|
return require(libPath);
|
package/lib/init/constants.js
CHANGED
|
@@ -5,7 +5,7 @@ module.exports = {
|
|
|
5
5
|
COMMAND_ADD: 'add',
|
|
6
6
|
|
|
7
7
|
JAVA_LTS_VERSIONS: [17, 21],
|
|
8
|
-
MAVEN_ARCHETYPE_VERSION: '3.0.
|
|
8
|
+
MAVEN_ARCHETYPE_VERSION: '3.0.1',
|
|
9
9
|
|
|
10
10
|
OPTIONS: Object.freeze({
|
|
11
11
|
COMPLETION: 'completion',
|
|
@@ -57,7 +57,10 @@ module.exports = {
|
|
|
57
57
|
LINT_DEV: 'lint:dev',
|
|
58
58
|
PIPELINE: 'pipeline',
|
|
59
59
|
TYPER: 'typer',
|
|
60
|
-
TYPESCRIPT: 'typescript'
|
|
60
|
+
TYPESCRIPT: 'typescript',
|
|
61
|
+
EVENT_QUEUE: 'event-queue',
|
|
62
|
+
COV2AP: 'cov2ap',
|
|
63
|
+
WEBSOCKET: 'websocket'
|
|
61
64
|
}),
|
|
62
65
|
|
|
63
66
|
REGEX_JAVA_PACKAGE: /^[a-z_][a-z0-9_]*(\.[a-z_][a-z0-9_]*)*$/g,
|
|
@@ -69,7 +72,10 @@ module.exports = {
|
|
|
69
72
|
SAMPLES: 'https://github.com/SAP-samples/cloud-cap-samples',
|
|
70
73
|
CAPIRE: 'https://cap.cloud.sap',
|
|
71
74
|
MAVEN_ARCHETYPE_HELP: `https://cap.cloud.sap/docs/java/developing-applications/building#the-maven-archetype`,
|
|
72
|
-
MAVEN_INSTALL_HELP: `https://maven.apache.org/install.html
|
|
75
|
+
MAVEN_INSTALL_HELP: `https://maven.apache.org/install.html`,
|
|
76
|
+
CAP_JS_COMMUNITY_ODATA_V2: 'https://github.com/cap-js-community/odata-v2-adapter',
|
|
77
|
+
CAP_JS_COMMUNITY_EVENT_QUEUE: 'https://github.com/cap-js-community/event-queue',
|
|
78
|
+
CAP_JS_COMMUNITY_WEBSOCKET: 'https://github.com/cap-js-community/websocket'
|
|
73
79
|
}),
|
|
74
80
|
|
|
75
81
|
PROJECT_FILES: ['package.json', 'pom.xml', '.cdsrc.json']
|
package/lib/init/index.js
CHANGED
|
@@ -22,14 +22,16 @@ module.exports = class CDSGenerator {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
static help({ exclude = [] } = {}) {
|
|
25
|
+
if (!cds.add) return // use case shell completion: cds.add is undefined and not needed
|
|
26
|
+
|
|
25
27
|
const plugins = this.readPlugins(exclude)
|
|
26
28
|
const nameFixedLength = Math.max(...plugins.map(plugin => plugin.name.length))
|
|
27
29
|
return plugins
|
|
30
|
+
.filter(({module}) => module.help())
|
|
28
31
|
.map(({name, module}) => {
|
|
29
|
-
|
|
30
|
-
return ` *${name}*${' '.repeat(nameFixedLength - name.length)} - ${help}`
|
|
32
|
+
return ` *${name}*${' '.repeat(nameFixedLength - name.length)} - ${module.help()}`
|
|
31
33
|
})
|
|
32
|
-
.join('\n
|
|
34
|
+
.join('\n')
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
/**
|
|
@@ -176,7 +178,7 @@ module.exports = class CDSGenerator {
|
|
|
176
178
|
const fuzzySearch = require('../../bin/util/fuzzySearch')
|
|
177
179
|
const [bestMatch] = fuzzySearch(plugin, entries.map(e => e.name))
|
|
178
180
|
const max = entries.reduce((max, {name}) => Math.max(max, name.length), 0)
|
|
179
|
-
const allFacetsText = entries.map(({name, module}) => {
|
|
181
|
+
const allFacetsText = entries.filter(({module})=> module.help()).map(({name, module}) => {
|
|
180
182
|
const help = module.help?.() ?? ''
|
|
181
183
|
return `${term.bold(name) + ' '.repeat(max - name.length)} ${term.dim(help)}`
|
|
182
184
|
}).join('\n')
|
|
@@ -4,6 +4,7 @@ const { fs, exists, isdir, path } = cds.utils
|
|
|
4
4
|
const { parseXml } = require('./xml')
|
|
5
5
|
const { REGEX_JAVA_VERSION, JAVA_LTS_VERSIONS } = require('./constants')
|
|
6
6
|
const DEBUG = cds.debug('cli')
|
|
7
|
+
const TRACE = cds.debug('trace')
|
|
7
8
|
|
|
8
9
|
module.exports = new class ProjectReader {
|
|
9
10
|
|
|
@@ -88,7 +89,7 @@ module.exports = new class ProjectReader {
|
|
|
88
89
|
* @property {boolean} hasAttachments Indicates if the project uses SAP BTP Object Store Service.
|
|
89
90
|
* @property {boolean} hasApprouter Indicates if the project uses the SAP Application Router.
|
|
90
91
|
* @property {boolean} hasHtml5Repo Indicates if the project uses the SAP BTP HTML5 Application Repository.
|
|
91
|
-
* @property {boolean} hasPortal Indicates if the project uses the SAP Cloud Portal
|
|
92
|
+
* @property {boolean} hasPortal Indicates if the project uses the SAP Cloud Portal service.
|
|
92
93
|
* @property {boolean} hasDestination Indicates if the project uses the SAP BTP Destination service.
|
|
93
94
|
* @property {boolean} hasMTXRoute Indicates if the approuter should have a multitenant route.
|
|
94
95
|
* @property {boolean} hasMTXRouteJava Indicates if the approuter should have a multitenant subscription route (java).
|
|
@@ -106,7 +107,7 @@ module.exports = new class ProjectReader {
|
|
|
106
107
|
if (!this.appName) this.#setPackageInfo()
|
|
107
108
|
const env = this.env4('production')
|
|
108
109
|
const { appVersion, appName, appId, appDescription, jdkVersion } = this
|
|
109
|
-
|
|
110
|
+
TRACE?.({ env })
|
|
110
111
|
|
|
111
112
|
const _inProd = plugin => require(`./template/${plugin}`).hasInProduction(env)
|
|
112
113
|
const _ui5 = () => exists('package.json') && !!JSON.parse(fs.readFileSync(path.resolve(cds.root, 'package.json'))).sapux
|
|
@@ -187,7 +188,6 @@ module.exports = new class ProjectReader {
|
|
|
187
188
|
})
|
|
188
189
|
})()
|
|
189
190
|
|
|
190
|
-
DEBUG?.({ project })
|
|
191
191
|
return project
|
|
192
192
|
}
|
|
193
193
|
}
|
|
@@ -36,7 +36,7 @@ module.exports = class AuditLoggingTemplate extends require('../../plugin') {
|
|
|
36
36
|
const { hasMta, hasHelm, hasMultitenancy, srvPath } = project
|
|
37
37
|
const { plan = 'premium' } = cds.cli.options
|
|
38
38
|
project.servicePlan = plan
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
if (hasMultitenancy) {
|
|
41
41
|
await merge(__dirname, 'files/package.sidecar.json').into('mtx/sidecar/package.json')
|
|
42
42
|
}
|
|
@@ -12,6 +12,10 @@ module.exports = class CfManifestTemplate extends require('../../plugin') {
|
|
|
12
12
|
return 'Cloud Foundry deployment using manifest files'
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
static hasInProduction() {
|
|
16
|
+
return exists(MANIFEST_FILE)
|
|
17
|
+
}
|
|
18
|
+
|
|
15
19
|
async canRun() {
|
|
16
20
|
if (cds.cli.options.force) {
|
|
17
21
|
return true
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const { readProject } = require('../../projectReader')
|
|
2
|
+
const { merge, sort } = require('../../merge')
|
|
3
|
+
const { link } = require('../../../util/term')
|
|
4
|
+
const { CAP_JS_COMMUNITY_ODATA_V2 } = require('../../constants').URLS
|
|
5
|
+
|
|
6
|
+
module.exports = class Cov2apTemplate extends require('../../plugin') {
|
|
7
|
+
|
|
8
|
+
static help() {
|
|
9
|
+
return 'cap-js-community: expose CDS services via the OData V2 protocol'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static hasInProduction() {
|
|
13
|
+
const { dependencies } = readProject()
|
|
14
|
+
return !!dependencies?.['@cap-js-community/odata-v2-adapter']
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async canRun() {
|
|
18
|
+
const { isJava } = readProject()
|
|
19
|
+
if (isJava) throw `'cds add cov2ap' is not available for Java`
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async run() {
|
|
24
|
+
await merge(__dirname, 'files/dependencies.package.json').into('package.json')
|
|
25
|
+
await sort('package.json', 'dependencies')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async finalize() {
|
|
29
|
+
console.log(`Find plugin documentation on ${link(CAP_JS_COMMUNITY_ODATA_V2)}`)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const { readProject } = require('../../projectReader')
|
|
2
|
+
const { merge, sort } = require('../../merge')
|
|
3
|
+
const { link } = require('../../../util/term')
|
|
4
|
+
const { CAP_JS_COMMUNITY_EVENT_QUEUE } = require('../../constants').URLS
|
|
5
|
+
|
|
6
|
+
module.exports = class EventQueueTemplate extends require('../../plugin') {
|
|
7
|
+
|
|
8
|
+
static help() {
|
|
9
|
+
return 'cap-js-community: transactional event processing with automatic load distribution'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async canRun() {
|
|
13
|
+
const { isJava } = readProject()
|
|
14
|
+
if (isJava) throw `'cds add event-queue' is not available for Java`
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static hasInProduction() {
|
|
19
|
+
const { dependencies } = readProject()
|
|
20
|
+
return !!dependencies?.['@cap-js-community/event-queue']
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
requires() {
|
|
24
|
+
return ['redis']
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async run() {
|
|
28
|
+
await merge(__dirname, 'files/dependencies.package.json').into('package.json')
|
|
29
|
+
await sort('package.json', 'dependencies')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async finalize() {
|
|
33
|
+
console.log(`Find plugin documentation on ${link(CAP_JS_COMMUNITY_EVENT_QUEUE)}`)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -149,6 +149,7 @@ module.exports = class HttpTemplate extends require('../../plugin') {
|
|
|
149
149
|
}
|
|
150
150
|
varsDefined = true
|
|
151
151
|
const headers = auth ? `Content-Type: application/json\n{{auth}}` : `Content-Type: application/json`
|
|
152
|
+
const headerAuth = auth ? '{{auth}}' : ''
|
|
152
153
|
|
|
153
154
|
service.urlPaths.forEach((urlPath) => {
|
|
154
155
|
if (service.urlPaths.length > 1) {
|
|
@@ -156,36 +157,41 @@ module.exports = class HttpTemplate extends require('../../plugin') {
|
|
|
156
157
|
}
|
|
157
158
|
|
|
158
159
|
service.entities.forEach((e) => {
|
|
159
|
-
const entityNameSimple = e.name.slice(
|
|
160
|
+
const entityNameSimple = e.name.slice(serviceName.length + 1)
|
|
161
|
+
const nameSlug = slug(entityNameSimple)
|
|
160
162
|
const url = `{{server}}/${urlPath}${entityNameSimple}`
|
|
161
|
-
const requestTitle = `\n### ${
|
|
162
|
-
requests.push(`${requestTitle}\nGET ${url}\n${
|
|
163
|
+
const requestTitle = `\n### ${entityNameSimple}`
|
|
164
|
+
requests.push(`${requestTitle}\n# @name ${nameSlug}_GET\nGET ${url}\n${headerAuth}\n`)
|
|
163
165
|
if (e.readOnly) return
|
|
164
166
|
|
|
165
167
|
const entityData = payload[`${e.name}`] ? payload[`${e.name}`][0] : null
|
|
166
168
|
if (!entityData) return
|
|
167
|
-
const postBody = JSON.stringify(entityData, null, 2)
|
|
168
|
-
|
|
169
|
-
let id
|
|
170
|
-
const compositeKey = Object.keys(e.keys).length > 1
|
|
171
|
-
if (compositeKey) {
|
|
172
|
-
id = e.keys.map(k => k.type === 'cds.String' ? `${k.name}='${entityData[k.name]}'` : `${k.name}=${entityData[k.name]}`).join(',')
|
|
173
|
-
} else {
|
|
174
|
-
id = entityData[Object.keys(e.keys)[0]]
|
|
175
|
-
}
|
|
176
169
|
|
|
177
170
|
if (e.isDraft) {
|
|
178
|
-
|
|
179
|
-
|
|
171
|
+
const key = Object.keys(e.keys)[0] // draft keys are always single
|
|
172
|
+
delete entityData[key] // draft keys should always by auto generated (either UUID or through custom logic), so remove them from the payload
|
|
173
|
+
const postBody = JSON.stringify(entityData, null, 2)
|
|
174
|
+
generateDraftRequests(requests, requestTitle, url, headers, headerAuth, postBody, key)
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
const postBody = JSON.stringify(entityData, null, 2)
|
|
178
|
+
let id
|
|
179
|
+
const compositeKey = Object.keys(e.keys).length > 1
|
|
180
|
+
if (compositeKey) {
|
|
181
|
+
id = e.keys.map(k => k.type === 'cds.String' ? `${k.name}='${entityData[k.name]}'` : `${k.name}=${entityData[k.name]}`).join(',')
|
|
182
|
+
} else {
|
|
183
|
+
id = entityData[Object.keys(e.keys)[0]]
|
|
184
|
+
}
|
|
180
185
|
compositeKey ? id = `(${id})` : id = `/${id}`
|
|
181
|
-
requests.push(`${requestTitle}\nPOST ${url}\n${headers}\n\n${postBody}\n`)
|
|
182
|
-
requests.push(`${requestTitle}\nPATCH ${url}${id}\n${headers}\n\n${postBody}\n`)
|
|
183
|
-
requests.push(`${requestTitle}\nDELETE ${url}${id}\n${headers}\n`)
|
|
186
|
+
requests.push(`${requestTitle}\n# @name ${nameSlug}_POST\nPOST ${url}\n${headers}\n\n${postBody}\n`)
|
|
187
|
+
requests.push(`${requestTitle}\n# @name ${nameSlug}_PATCH\nPATCH ${url}${id}\n${headers}\n\n${postBody}\n`)
|
|
188
|
+
requests.push(`${requestTitle}\n# @name ${nameSlug}_DELETE\nDELETE ${url}${id}\n${headers}\n`)
|
|
184
189
|
}
|
|
185
190
|
})
|
|
186
191
|
|
|
187
192
|
service.actions.forEach((a) => {
|
|
188
|
-
const
|
|
193
|
+
const simpleNameAction = a.name.slice(serviceName.length + 1)
|
|
194
|
+
const url = `{{server}}/${urlPath}${simpleNameAction}`
|
|
189
195
|
|
|
190
196
|
let actionPayload = {}
|
|
191
197
|
if (a.params) {
|
|
@@ -197,20 +203,25 @@ module.exports = class HttpTemplate extends require('../../plugin') {
|
|
|
197
203
|
: null
|
|
198
204
|
}
|
|
199
205
|
}
|
|
200
|
-
requests.push(`\n### ${
|
|
206
|
+
requests.push(`\n### ${simpleNameAction}\n# @name ${simpleNameAction}_POST\nPOST ${url}\n${headers}\n\n${JSON.stringify(actionPayload, null, 2)}\n`)
|
|
201
207
|
})
|
|
202
208
|
|
|
203
209
|
})
|
|
204
210
|
this.#write(requests, serviceName)
|
|
205
211
|
}
|
|
206
212
|
|
|
207
|
-
function generateDraftRequests(requests,
|
|
208
|
-
|
|
209
|
-
requests.push(`${
|
|
210
|
-
requests.push(
|
|
211
|
-
requests.push(
|
|
212
|
-
requests.push(`${
|
|
213
|
-
requests.push(`${
|
|
213
|
+
function generateDraftRequests(requests, title, url, headers, headerAuth, postBody, key) {
|
|
214
|
+
const nameDraftsCreate = slug(title+'_Draft_POST')
|
|
215
|
+
requests.push(`${title} Drafts GET\n# @name ${slug(title+'_Drafts_GET')}\nGET ${url}?$filter=(IsActiveEntity eq false)\n${headerAuth}\n`) // unencoded URL for better readability (client can handle this)
|
|
216
|
+
requests.push(`${title} Draft POST\n# @name ${nameDraftsCreate}\nPOST ${url}\n${headers}\n\n${postBody}\n`)
|
|
217
|
+
requests.push(`\n### Result from POST request above\n@draftID = {{${nameDraftsCreate}.response.body.$.${key}}}\n`)
|
|
218
|
+
requests.push(`${title} Draft PATCH\n# @name ${slug(title+'_Draft_Patch')}\nPATCH ${url}(${key}={{draftID}},IsActiveEntity=false)\n${headers}\n\n${postBody}\n`)
|
|
219
|
+
requests.push(`${title} Draft Prepare\n# @name ${slug(title+'_Draft_Prepare')}\nPOST ${url}(${key}={{draftID}},IsActiveEntity=false)/AdminService.draftPrepare\n${headers}\n\n{}\n`)
|
|
220
|
+
requests.push(`${title} Draft Activate\n# @name ${slug(title+'_Draft_Activate')}\nPOST ${url}(${key}={{draftID}},IsActiveEntity=false)/AdminService.draftActivate\n${headers}\n\n{}\n`)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function slug(str) {
|
|
224
|
+
return str.trim().replace(/[#\\. ]/g, '')
|
|
214
225
|
}
|
|
215
226
|
}
|
|
216
227
|
|
|
@@ -248,5 +259,3 @@ module.exports = class HttpTemplate extends require('../../plugin') {
|
|
|
248
259
|
return `${outputDir}/${serviceName}.http`
|
|
249
260
|
}
|
|
250
261
|
}
|
|
251
|
-
|
|
252
|
-
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
const cds = require('@sap/cds
|
|
1
|
+
const cds = require('@sap/cds')
|
|
2
2
|
|
|
3
3
|
module.exports = class AdminService extends cds.ApplicationService { init(){
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
const { Books } = this.entities
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generate IDs for new Books drafts
|
|
9
|
+
*/
|
|
10
|
+
this.before ('NEW', Books.drafts, async (req) => {
|
|
11
|
+
if (req.data.ID) return
|
|
12
|
+
const { ID:id1 } = await SELECT.one.from(Books).columns('max(ID) as ID')
|
|
13
|
+
const { ID:id2 } = await SELECT.one.from(Books.drafts).columns('max(ID) as ID')
|
|
14
|
+
req.data.ID = Math.max(id1||0, id2||0) + 1
|
|
15
|
+
})
|
|
5
16
|
return super.init()
|
|
6
17
|
}}
|
|
7
|
-
|
|
8
|
-
/** Generate primary keys for target entity in request */
|
|
9
|
-
async function genid (req) {
|
|
10
|
-
const {ID} = await cds.tx(req).run (SELECT.one.from(req.target.actives).columns('max(ID) as ID'))
|
|
11
|
-
req.data.ID = ID - ID % 100 + 100 + 1
|
|
12
|
-
}
|
|
@@ -1,26 +1,38 @@
|
|
|
1
1
|
const cds = require('@sap/cds')
|
|
2
|
-
const { Books } = cds.entities ('sap.capire.bookshop')
|
|
3
2
|
|
|
4
|
-
class CatalogService extends cds.ApplicationService { init(){
|
|
3
|
+
class CatalogService extends cds.ApplicationService { init() {
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const {book,quantity} = req.data
|
|
9
|
-
let {stock} = await SELECT `stock` .from (Books,book)
|
|
10
|
-
if (stock >= quantity) {
|
|
11
|
-
await UPDATE (Books,book) .with (`stock -=`, quantity)
|
|
12
|
-
await this.emit ('OrderedBook', { book, quantity, buyer:req.user.id })
|
|
13
|
-
return { stock }
|
|
14
|
-
}
|
|
15
|
-
else return req.error (409,`${quantity} exceeds stock for book #${book}`)
|
|
16
|
-
})
|
|
5
|
+
const { Books } = cds.entities('sap.capire.bookshop')
|
|
6
|
+
const { ListOfBooks } = this.entities
|
|
17
7
|
|
|
18
8
|
// Add some discount for overstocked books
|
|
19
|
-
this.after
|
|
9
|
+
this.after('each', ListOfBooks, book => {
|
|
20
10
|
if (book.stock > 111) book.title += ` -- 11% discount!`
|
|
21
11
|
})
|
|
22
12
|
|
|
13
|
+
// Reduce stock of ordered books if available stock suffices
|
|
14
|
+
this.on('submitOrder', async req => {
|
|
15
|
+
let { book:id, quantity } = req.data
|
|
16
|
+
let book = await SELECT.from (Books, id, b => b.stock)
|
|
17
|
+
|
|
18
|
+
// Validate input data
|
|
19
|
+
if (!book) return req.error (404, `Book #${id} doesn't exist`)
|
|
20
|
+
if (quantity < 1) return req.error (400, `quantity has to be 1 or more`)
|
|
21
|
+
if (quantity > book.stock) return req.error (409, `${quantity} exceeds stock for book #${id}`)
|
|
22
|
+
|
|
23
|
+
// Reduce stock in database and return updated stock value
|
|
24
|
+
await UPDATE (Books, id) .with ({ stock: book.stock -= quantity })
|
|
25
|
+
return book
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// Emit event when an order has been submitted
|
|
29
|
+
this.after('submitOrder', async (_,req) => {
|
|
30
|
+
let { book, quantity } = req.data
|
|
31
|
+
await this.emit('OrderedBook', { book, quantity, buyer: req.user.id })
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Delegate requests to the underlying generic service
|
|
23
35
|
return super.init()
|
|
24
36
|
}}
|
|
25
37
|
|
|
26
|
-
module.exports =
|
|
38
|
+
module.exports = CatalogService
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const { readProject } = require('../../projectReader')
|
|
2
|
+
const { merge, sort } = require('../../merge')
|
|
3
|
+
const { link } = require('../../../util/term')
|
|
4
|
+
const { CAP_JS_COMMUNITY_WEBSOCKET } = require('../../constants').URLS
|
|
5
|
+
|
|
6
|
+
module.exports = class WebsocketTemplate extends require('../../plugin') {
|
|
7
|
+
|
|
8
|
+
static help() {
|
|
9
|
+
return 'cap-js-community: expose CDS services via the Websocket protocol'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async canRun() {
|
|
13
|
+
const { isJava } = readProject()
|
|
14
|
+
if (isJava) throw `'cds add websocket' is not available for Java`
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
requires() {
|
|
19
|
+
return ['redis']
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static hasInProduction() {
|
|
23
|
+
const { dependencies } = readProject()
|
|
24
|
+
return !!dependencies?.['@cap-js-community/websocket']
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async run() {
|
|
28
|
+
await merge(__dirname, 'files/dependencies.package.json').into('package.json')
|
|
29
|
+
await sort('package.json', 'dependencies')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async finalize() {
|
|
33
|
+
console.log(`Find plugin documentation on ${link(CAP_JS_COMMUNITY_WEBSOCKET)}`)
|
|
34
|
+
}
|
|
35
|
+
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds-dk",
|
|
3
|
-
"version": "8.0
|
|
3
|
+
"version": "8.1.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@sap/cds-dk",
|
|
9
|
-
"version": "8.0
|
|
9
|
+
"version": "8.1.0",
|
|
10
10
|
"license": "SEE LICENSE IN LICENSE",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@cap-js/asyncapi": "^1.0.0",
|
|
@@ -42,9 +42,12 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"node_modules/@cap-js/db-service": {
|
|
45
|
-
"version": "1.
|
|
45
|
+
"version": "1.12.0",
|
|
46
46
|
"license": "SEE LICENSE",
|
|
47
47
|
"optional": true,
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"generic-pool": "^3.9.0"
|
|
50
|
+
},
|
|
48
51
|
"peerDependencies": {
|
|
49
52
|
"@sap/cds": ">=7.9"
|
|
50
53
|
}
|
|
@@ -102,7 +105,7 @@
|
|
|
102
105
|
}
|
|
103
106
|
},
|
|
104
107
|
"node_modules/@eslint/config-array": {
|
|
105
|
-
"version": "0.17.
|
|
108
|
+
"version": "0.17.1",
|
|
106
109
|
"license": "Apache-2.0",
|
|
107
110
|
"dependencies": {
|
|
108
111
|
"@eslint/object-schema": "^2.1.4",
|
|
@@ -135,7 +138,7 @@
|
|
|
135
138
|
}
|
|
136
139
|
},
|
|
137
140
|
"node_modules/@eslint/js": {
|
|
138
|
-
"version": "9.
|
|
141
|
+
"version": "9.8.0",
|
|
139
142
|
"license": "MIT",
|
|
140
143
|
"engines": {
|
|
141
144
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
|
@@ -200,7 +203,7 @@
|
|
|
200
203
|
}
|
|
201
204
|
},
|
|
202
205
|
"node_modules/@sap/cds": {
|
|
203
|
-
"version": "8.0
|
|
206
|
+
"version": "8.1.0",
|
|
204
207
|
"license": "SEE LICENSE IN LICENSE",
|
|
205
208
|
"dependencies": {
|
|
206
209
|
"@sap/cds-compiler": ">=5",
|
|
@@ -226,7 +229,7 @@
|
|
|
226
229
|
}
|
|
227
230
|
},
|
|
228
231
|
"node_modules/@sap/cds-compiler": {
|
|
229
|
-
"version": "5.0
|
|
232
|
+
"version": "5.1.0",
|
|
230
233
|
"license": "SEE LICENSE IN LICENSE",
|
|
231
234
|
"dependencies": {
|
|
232
235
|
"antlr4": "4.9.3"
|
|
@@ -866,14 +869,14 @@
|
|
|
866
869
|
}
|
|
867
870
|
},
|
|
868
871
|
"node_modules/eslint": {
|
|
869
|
-
"version": "9.
|
|
872
|
+
"version": "9.8.0",
|
|
870
873
|
"license": "MIT",
|
|
871
874
|
"dependencies": {
|
|
872
875
|
"@eslint-community/eslint-utils": "^4.2.0",
|
|
873
876
|
"@eslint-community/regexpp": "^4.11.0",
|
|
874
|
-
"@eslint/config-array": "^0.17.
|
|
877
|
+
"@eslint/config-array": "^0.17.1",
|
|
875
878
|
"@eslint/eslintrc": "^3.1.0",
|
|
876
|
-
"@eslint/js": "9.
|
|
879
|
+
"@eslint/js": "9.8.0",
|
|
877
880
|
"@humanwhocodes/module-importer": "^1.0.1",
|
|
878
881
|
"@humanwhocodes/retry": "^0.3.0",
|
|
879
882
|
"@nodelib/fs.walk": "^1.2.8",
|
|
@@ -2422,7 +2425,7 @@
|
|
|
2422
2425
|
}
|
|
2423
2426
|
},
|
|
2424
2427
|
"node_modules/yaml": {
|
|
2425
|
-
"version": "2.
|
|
2428
|
+
"version": "2.5.0",
|
|
2426
2429
|
"license": "ISC",
|
|
2427
2430
|
"bin": {
|
|
2428
2431
|
"yaml": "bin.mjs"
|