@sap/cds-dk 8.4.0 → 8.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/bin/compile/index.js +6 -1
- package/bin/deploy/index.js +6 -1
- package/lib/compile/to-xsuaa.js +39 -26
- package/lib/deploy/to-hana/hana.js +60 -53
- package/lib/init/constants.js +1 -1
- package/lib/init/template/application-logging/files/package.json.hbs +6 -2
- package/lib/init/template/application-logging/index.js +4 -4
- package/lib/init/template/approuter/files/package.json +1 -1
- package/lib/init/template/data/as-csv.js +1 -1
- package/lib/init/template/data/as-json.js +46 -42
- package/lib/init/template/hana/files/db/package.json +1 -1
- package/lib/init/template/http/index.js +6 -7
- package/lib/init/template/typer/index.js +0 -1
- package/lib/mtx/push.js +0 -1
- package/npm-shrinkwrap.json +34 -23
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,10 +7,29 @@ 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.4.1 - 2024-11-08
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `cds deploy --no-build` lets you skip the implicit `cds build`.
|
|
15
|
+
- `cds add data/http` supports new type `cds.Map`.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- `cds compile --to <unknown>` shows a cleaner error message.
|
|
22
|
+
- `cds add data` handles composition of one correctly.
|
|
23
|
+
- `cds add http` creates payloads for unbound actions in services.
|
|
24
|
+
- `cds compile --to xsuaa` can now handle annotation expressions in `where` clauses.
|
|
25
|
+
- `cds add http` produces requests for all expected data and no longer relies on existing data on the side.
|
|
26
|
+
- `cds add typer` creates `tsconfig.json` that works with `cds-tsx`.
|
|
27
|
+
|
|
10
28
|
## Version 8.4.0 - 2024-10-29
|
|
11
29
|
|
|
12
30
|
### Added
|
|
13
31
|
|
|
32
|
+
- `cds add application-logging` for Java also executes the `cds:add` goal with feature `APPLICATION_LOGGING`.
|
|
14
33
|
- Added support for `import` and `export` for auto-completion in `package.json` and `.cdsrc.json`.
|
|
15
34
|
- `cds add side-by-side-extensibility` adds configuration for logic extensibility via extension points (Java).
|
|
16
35
|
- `cds init --java` can be used as an alias to `cds init --add java`.
|
package/bin/compile/index.js
CHANGED
|
@@ -238,6 +238,12 @@ async function compile (models, options={}) {
|
|
|
238
238
|
if (/mta.*\.ya?ml/.test(models[0])) {
|
|
239
239
|
model = Promise.resolve(models[0])
|
|
240
240
|
} else {
|
|
241
|
+
for (const kind of ['to', 'for']) {
|
|
242
|
+
const tail = options[kind]
|
|
243
|
+
if (typeof tail === 'string' && !cds.compile[kind][tail]) {
|
|
244
|
+
throw `Unknown model processor: cds.compile.${kind}.${tail}`
|
|
245
|
+
}
|
|
246
|
+
}
|
|
241
247
|
model = cds.load (models, options)
|
|
242
248
|
src = models[0] .replace (/\.[^.]+$/,'') //> excluding source file extension, e.g. .cds
|
|
243
249
|
}
|
|
@@ -305,7 +311,6 @@ async function compile (models, options={}) {
|
|
|
305
311
|
let p = cds.compile[kind]
|
|
306
312
|
for (let each of tail.split('.')) {
|
|
307
313
|
p = p[each]
|
|
308
|
-
if (!p) throw `Unknown model processor: cds.compile.${kind}.${tail}`
|
|
309
314
|
}
|
|
310
315
|
return p
|
|
311
316
|
}
|
package/bin/deploy/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module.exports = Object.assign(deploy, {
|
|
2
2
|
options: ['--to', '--for', '--out', '--tunnel-address', '--vcap-file', '--delta-from' ],
|
|
3
|
-
flags: ['--no-save', '--auto-undeploy', '--dry', '--xdry', '--with-mocks', '--store-credentials', '--model-only', '--script', '--with-auto-schema-evolution' ],
|
|
3
|
+
flags: ['--no-save', '--auto-undeploy', '--dry', '--xdry', '--with-mocks', '--store-credentials', '--no-build', '--model-only', '--script', '--with-auto-schema-evolution' ],
|
|
4
4
|
shortcuts: ['-2', '-4', '-o'],
|
|
5
5
|
help: `
|
|
6
6
|
# SYNOPSIS
|
|
@@ -37,6 +37,10 @@ module.exports = Object.assign(deploy, {
|
|
|
37
37
|
|
|
38
38
|
Just print the SQL statements without executing them.
|
|
39
39
|
|
|
40
|
+
*--no-build* (only in combination with *--to hana*)
|
|
41
|
+
|
|
42
|
+
Skip running *cds build*.
|
|
43
|
+
|
|
40
44
|
*--out* | *-o* <file>
|
|
41
45
|
|
|
42
46
|
Write the DDL statements to the given file.
|
|
@@ -97,6 +101,7 @@ module.exports = Object.assign(deploy, {
|
|
|
97
101
|
cds deploy --to sqlite:db
|
|
98
102
|
cds deploy --to hana
|
|
99
103
|
cds deploy --to hana:myService --auto-undeploy
|
|
104
|
+
cds deploy --to hana --no-build
|
|
100
105
|
|
|
101
106
|
`})
|
|
102
107
|
|
package/lib/compile/to-xsuaa.js
CHANGED
|
@@ -7,44 +7,57 @@ const hardcoded = {
|
|
|
7
7
|
Attributes: ['id', 'tenant', 'ext_attr']
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
11
10
|
module.exports = function compile_to_xsuaa (model, options) {
|
|
12
11
|
|
|
13
12
|
const base = getBaseModel(options)
|
|
14
13
|
const scopes = {}
|
|
15
14
|
const attributes = {}
|
|
16
15
|
|
|
16
|
+
const findUserAttrInExpression = (expr, attributes = []) => {
|
|
17
|
+
// find `$user` ref, but not e.g. `$self.$user`
|
|
18
|
+
if (typeof expr === 'object' && expr.ref?.length && expr.ref[0] === '$user') {
|
|
19
|
+
if (expr.ref.length === 2) {
|
|
20
|
+
const attr = expr.ref[1]
|
|
21
|
+
if (!/^[a-zA-Z0-9_]*$/.test(attr)) {
|
|
22
|
+
// see https://help.sap.com/docs/CP_AUTHORIZ_TRUST_MNG/ae8e8427ecdf407790d96dad93b5f723/517895a9612241259d6941dbf9ad81cb.html#attributes
|
|
23
|
+
throw new Error(`Illegal attribute name '${attr}' in '${expr}'. Only characters a-z, A-Z, 0-9, _ are allowed.`)
|
|
24
|
+
}
|
|
25
|
+
if (!hardcoded.Attributes.includes(attr)) attributes.push(attr)
|
|
26
|
+
}
|
|
27
|
+
// Reject expressions like `$user.foo.bar`, as xsuaa attributes are primitive, not structured
|
|
28
|
+
else if (expr.ref.length > 2) {
|
|
29
|
+
throw new Error (`Structured attributes like '${expr.ref.join('.')}' are not supported`)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (typeof expr === 'object') { // descend sub expressions etc.
|
|
33
|
+
Object.values(expr).forEach(e => findUserAttrInExpression(e, attributes))
|
|
34
|
+
}
|
|
35
|
+
else if (Array.isArray(expr)) { // descend arrays
|
|
36
|
+
expr.forEach(e => findUserAttrInExpression(e, attributes))
|
|
37
|
+
}
|
|
38
|
+
return attributes
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {string|object} condition - the condition object or string
|
|
43
|
+
* @returns string[] - the attributes found in the condition
|
|
44
|
+
*/
|
|
17
45
|
const parseAttributes = (condition) => { // e.g. 'foo = $user.bar or baz = $user.boo'
|
|
18
46
|
if (!condition) return []
|
|
19
47
|
|
|
20
|
-
// first validate basic syntax of expression by parsing it
|
|
21
48
|
let expr
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const userIdx = expr.ref?.indexOf('$user')
|
|
31
|
-
if (userIdx >= 0 && expr.ref.length > userIdx+2) {
|
|
32
|
-
throw new Error (`Structured attributes like '${expr.ref.join('.')}' are not supported`)
|
|
49
|
+
if (typeof condition === 'object') {
|
|
50
|
+
if (!condition.xpr && !condition['=']) throw new Error(`Invalid expression: ${JSON.stringify(condition)}`)
|
|
51
|
+
expr = condition.xpr
|
|
52
|
+
} else {
|
|
53
|
+
try {
|
|
54
|
+
expr = cds.parse.expr(condition).xpr
|
|
55
|
+
} catch (err) {
|
|
56
|
+
throw new Error(`Invalid expression': ${err.message}`, { cause: err })
|
|
33
57
|
}
|
|
34
|
-
}
|
|
58
|
+
}
|
|
35
59
|
|
|
36
|
-
|
|
37
|
-
// Impl. note: use the expression string rather than the parsed expression as the above example would be
|
|
38
|
-
// `[ { ref: [ '$user', 'foo' ] }, '-', { ref: [ 'bar' ] } ]`
|
|
39
|
-
// making it ambiguous to tell if it was `$user.foo-bar` or `$user.foo - bar`
|
|
40
|
-
return Array.from(condition.matchAll(/\$user\s*\.\s*(\S*)/g))
|
|
41
|
-
.map(m => m[1])
|
|
42
|
-
.filter(m => !hardcoded.Attributes.includes(m))
|
|
43
|
-
.map(m => {
|
|
44
|
-
// see https://help.sap.com/docs/CP_AUTHORIZ_TRUST_MNG/ae8e8427ecdf407790d96dad93b5f723/517895a9612241259d6941dbf9ad81cb.html#attributes
|
|
45
|
-
if (!/^[a-zA-Z0-9_]*$/.test(m)) throw new Error(`Illegal attribute name '${m}' in '${condition}'. Only characters a-z, A-Z, 0-9, _ are allowed.`)
|
|
46
|
-
return m
|
|
47
|
-
})
|
|
60
|
+
return findUserAttrInExpression(expr)
|
|
48
61
|
}
|
|
49
62
|
|
|
50
63
|
cds.reflect(model).foreach(def => {
|
|
@@ -3,7 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const fs = require('fs').promises;
|
|
4
4
|
|
|
5
5
|
const cds = require('../../../lib/cds');
|
|
6
|
-
const { exists, read } = cds.utils;
|
|
6
|
+
const { exists, read, write, mkdirp } = cds.utils;
|
|
7
7
|
|
|
8
8
|
const { build } = require('../../build');
|
|
9
9
|
|
|
@@ -67,68 +67,73 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
let buildResult
|
|
71
|
+
if (cds.cli.options?.['no-build']) {
|
|
72
|
+
console.log(`Provided flag '--no-build', skipping cds build`)
|
|
73
|
+
} else {
|
|
74
|
+
buildResult = await this._build();
|
|
75
|
+
}
|
|
71
76
|
|
|
72
77
|
if (process.env.TARGET_CONTAINER) {
|
|
73
78
|
vcapEnv.TARGET_CONTAINER = process.env.TARGET_CONTAINER;
|
|
74
79
|
}
|
|
75
80
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
if (dry) {
|
|
82
|
+
const sortedResult = Array.from(buildResult.hana).sort();
|
|
83
|
+
for (const file of sortedResult) {
|
|
84
|
+
const srcPath = path.join(buildResult.dest, file);
|
|
85
|
+
console.log(`-- ${path.relative(cds.root, srcPath)}`);
|
|
86
|
+
console.log(await read(srcPath));
|
|
87
|
+
console.log();
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
// the primary database is always built and deployed
|
|
91
|
+
const dest = path.join(cds.env.build.target, cds.env.folders.db);
|
|
92
|
+
|
|
93
|
+
let serviceName = pServiceName;
|
|
94
|
+
let serviceKeyName;
|
|
95
|
+
const { instance, key } = cds.requires.db?.binding ?? {};
|
|
96
|
+
if (!serviceName && instance) {
|
|
97
|
+
serviceName = instance;
|
|
98
|
+
serviceKeyName = key;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (undeployWhitelist) {
|
|
102
|
+
console.log('Writing undeploy.json');
|
|
103
|
+
await write(undeployWhitelist).to(dest, 'undeploy.json')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const hasVCAPEnv = Object.keys(vcapEnv).length > 0;
|
|
107
|
+
if (!serviceName && hasVCAPEnv) {
|
|
108
|
+
await mkdirp(dest);
|
|
85
109
|
} else {
|
|
86
|
-
const
|
|
110
|
+
const { cfServiceInstanceName, cfServiceInstanceKeyName, serviceKey } =
|
|
111
|
+
await this._getOrCreateCFService(dest, serviceName, serviceKeyName, tunnelAddress);
|
|
87
112
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (!
|
|
91
|
-
|
|
92
|
-
serviceKeyName = cds.env.requires.db.binding.key;
|
|
113
|
+
serviceName ??= cfServiceInstanceName;
|
|
114
|
+
serviceKeyName ??= cfServiceInstanceKeyName
|
|
115
|
+
if (!vcapEnv.VCAP_SERVICES || serviceName) {
|
|
116
|
+
vcapEnv.VCAP_SERVICES = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
|
|
93
117
|
}
|
|
94
118
|
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
await fs.write(path.join(dest, 'undeploy.json'), JSON.stringify(undeployWhitelist, null, 2));
|
|
119
|
+
if (storeCredentials) {
|
|
120
|
+
await this._addInstanceToDefaultEnvJson([dest, cds.root], cfServiceInstanceName, serviceKey);
|
|
98
121
|
}
|
|
122
|
+
}
|
|
99
123
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
} else {
|
|
104
|
-
const { cfServiceInstanceName, cfServiceInstanceKeyName, serviceKey } =
|
|
105
|
-
await this._getOrCreateCFService(dest, serviceName, serviceKeyName, tunnelAddress);
|
|
106
|
-
|
|
107
|
-
serviceName ??= cfServiceInstanceName;
|
|
108
|
-
serviceKeyName ??= cfServiceInstanceKeyName
|
|
109
|
-
if (!vcapEnv.VCAP_SERVICES || serviceName) {
|
|
110
|
-
vcapEnv.VCAP_SERVICES = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (storeCredentials) {
|
|
114
|
-
await this._addInstanceToDefaultEnvJson([dest, cds.root], cfServiceInstanceName, serviceKey);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
124
|
+
console.log(`${bold('Starting deployment to SAP HANA ...')}`);
|
|
125
|
+
DEBUG?.({ dest, vcapEnv, hdiOptions })
|
|
126
|
+
await hdi.deploy(dest, vcapEnv, hdiOptions);
|
|
117
127
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
...options,
|
|
125
|
-
to: `${serviceName}:${serviceKeyName}`,
|
|
126
|
-
for: options.for,
|
|
127
|
-
kind: task.for
|
|
128
|
-
}
|
|
129
|
-
const services = await bind(bindOptions);
|
|
130
|
-
await storeBindings(services, bindOptions);
|
|
128
|
+
if (!hasVCAPEnv) {
|
|
129
|
+
const bindOptions = {
|
|
130
|
+
...options,
|
|
131
|
+
to: `${serviceName}:${serviceKeyName}`,
|
|
132
|
+
for: options.for,
|
|
133
|
+
kind: BUILD_TASK_HANA
|
|
131
134
|
}
|
|
135
|
+
const services = await bind(bindOptions);
|
|
136
|
+
await storeBindings(services, bindOptions);
|
|
132
137
|
}
|
|
133
138
|
|
|
134
139
|
if (storeCredentials) {
|
|
@@ -145,7 +150,7 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
|
|
|
145
150
|
console.warn(warn(warnings.join('\n')));
|
|
146
151
|
}
|
|
147
152
|
|
|
148
|
-
return
|
|
153
|
+
return buildResult;
|
|
149
154
|
}
|
|
150
155
|
|
|
151
156
|
|
|
@@ -206,9 +211,11 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
|
|
|
206
211
|
src: cds.env.folders.db,
|
|
207
212
|
[OUTPUT_MODE]: OUTPUT_MODE_FILESYSTEM | OUTPUT_MODE_RESULT
|
|
208
213
|
}
|
|
209
|
-
|
|
210
214
|
console.log(`Running build`);
|
|
211
|
-
|
|
215
|
+
|
|
216
|
+
// cds build throws an error if the src folder does not exist
|
|
217
|
+
const buildResult = await build(buildTaskOptions);
|
|
218
|
+
return buildResult[0].result;
|
|
212
219
|
}
|
|
213
220
|
|
|
214
221
|
async _addInstanceToDefaultEnvJson(currentFolders, serviceInstanceName, serviceKey) {
|
package/lib/init/constants.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { readProject } = require('../../projectReader')
|
|
2
2
|
const { merge } = require('../../merge')
|
|
3
3
|
const { srv4, mtxSidecar4, applicationLogging, approuter } = require('../../registries/mta')
|
|
4
|
+
const mvn = require('../../mvn')
|
|
4
5
|
|
|
5
6
|
module.exports = class ApplicationLoggingTemplate extends require('../../plugin') {
|
|
6
7
|
|
|
@@ -20,10 +21,9 @@ module.exports = class ApplicationLoggingTemplate extends require('../../plugin'
|
|
|
20
21
|
|
|
21
22
|
async run() {
|
|
22
23
|
const project = readProject()
|
|
23
|
-
const {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
24
|
+
const { isJava, configFile } = project
|
|
25
|
+
await merge(__dirname, 'files/package.json.hbs').into(configFile, { with: project })
|
|
26
|
+
if (isJava) await mvn.add('APPLICATION_LOGGING')
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
async combine() {
|
|
@@ -47,7 +47,7 @@ module.exports = async (data={}, csn, { headerOnly=false, separator=',' }={}) =>
|
|
|
47
47
|
}
|
|
48
48
|
else {
|
|
49
49
|
if (i === 0) header.push(name)
|
|
50
|
-
if (Array.isArray(value)) { // `array of`
|
|
50
|
+
if (Array.isArray(value) || typeof value === 'object') { // `array of`/`many` and `cds.Map`
|
|
51
51
|
value = JSON.stringify(value)
|
|
52
52
|
}
|
|
53
53
|
value = escape(value)
|
|
@@ -10,35 +10,37 @@ const MAX_ASSOC_DEPTH = 5
|
|
|
10
10
|
*
|
|
11
11
|
* @param {Record<string, object[]>|undefined} data the data with entities and their records to be created
|
|
12
12
|
* @param {object} csn the model
|
|
13
|
-
* @param {Number} numRecords the number of records to be created for each
|
|
13
|
+
* @param {Number} numRecords the number of records to be created for each artifact
|
|
14
14
|
* @param {{referenceData? : Record<string, string}} options misc. options
|
|
15
15
|
* @returns {Record<string, object[]>} the filled data
|
|
16
16
|
*/
|
|
17
17
|
const asJson = module.exports = async (data, csn, numRecords=1, options={}) => {
|
|
18
|
-
let { referenceData, contentType } = options
|
|
18
|
+
let { referenceData, contentType, artifact='entity' } = options
|
|
19
19
|
|
|
20
|
-
// if no
|
|
21
|
-
if (!data)
|
|
20
|
+
// if no data is given, use all data
|
|
21
|
+
if (!data) {
|
|
22
|
+
data = csn.all(artifact).reduce((all, e) => { all[e.name] = []; return all }, {})
|
|
23
|
+
}
|
|
22
24
|
|
|
23
25
|
// if no reference data is given, scan for it using cds.deploy.resources
|
|
24
26
|
if (!referenceData) referenceData = await cds.deploy.resources(csn)
|
|
25
27
|
|
|
26
28
|
// sort all entities first that are referenced by others, so that dependant entities can be created after them
|
|
27
29
|
const { definitions: defs } = csn
|
|
28
|
-
const
|
|
29
|
-
const n1HasComp = Object.values(defs[n1].elements).some(el => el.type === 'cds.Composition')
|
|
30
|
-
const n2HasComp = Object.values(defs[n2].elements).some(el => el.type === 'cds.Composition')
|
|
30
|
+
const names = Object.keys(data).sort((n1, n2) => {
|
|
31
|
+
const n1HasComp = Object.values(defs[n1].elements??[]).some(el => el.type === 'cds.Composition')
|
|
32
|
+
const n2HasComp = Object.values(defs[n2].elements??[]).some(el => el.type === 'cds.Composition')
|
|
31
33
|
if (n1HasComp && !n2HasComp) return -1
|
|
32
34
|
if (!n1HasComp && n2HasComp) return 1
|
|
33
35
|
return 0
|
|
34
36
|
})
|
|
35
37
|
|
|
36
38
|
// requested entities shall always be created, so remove it from reference data
|
|
37
|
-
Object.entries(referenceData).filter(([, name]) =>
|
|
39
|
+
Object.entries(referenceData).filter(([, name]) => names.includes(name)).forEach(([file]) => delete referenceData[file])
|
|
38
40
|
|
|
39
41
|
addSemantics(csn)
|
|
40
42
|
|
|
41
|
-
for (const name of
|
|
43
|
+
for (const name of names) {
|
|
42
44
|
if (!data[name] // removed on purpose because records are created inline through compositions
|
|
43
45
|
|| data[name].length >= numRecords) { // numRecords already created by following associations
|
|
44
46
|
continue
|
|
@@ -47,26 +49,26 @@ const asJson = module.exports = async (data, csn, numRecords=1, options={}) => {
|
|
|
47
49
|
let numRecordsToGenerate // numRecords might be reduced if some records were created by following associations
|
|
48
50
|
let records = []
|
|
49
51
|
if (data[name].length > 0) {
|
|
50
|
-
numRecordsToGenerate = numRecords - data[name].length
|
|
52
|
+
numRecordsToGenerate = numRecords - data[name].length
|
|
51
53
|
records = Array(numRecordsToGenerate).fill({}).map(() => ({}))
|
|
52
54
|
} else {
|
|
53
55
|
numRecordsToGenerate = numRecords
|
|
54
56
|
records = data[name] = Array(numRecordsToGenerate).fill({}).map(() => ({}))
|
|
55
57
|
}
|
|
56
|
-
const
|
|
58
|
+
const artifact = defs[name]
|
|
57
59
|
|
|
58
60
|
const idNum = randomNumber()
|
|
59
61
|
for (let i = 0; i < numRecordsToGenerate; i++) {
|
|
60
|
-
fillRecord(records[i], idNum+i,
|
|
62
|
+
fillRecord(records[i], idNum+i, artifact, csn, data, null, 0, referenceData)
|
|
61
63
|
}
|
|
62
|
-
data[name] = data[name].concat(records) // append the new records to the possibly
|
|
64
|
+
data[name] = data[name].concat(records) // append the new records to the possibly existing and referenced records
|
|
63
65
|
// remove records w/ duplicate keys. This can happen if the key is an enum (and we have less enum values than requested records).
|
|
64
|
-
data[name] = uniqRecords(data[name],
|
|
66
|
+
data[name] = uniqRecords(data[name], artifact)
|
|
65
67
|
}
|
|
66
68
|
for (let [key, d] of Object.entries(data)) {
|
|
67
69
|
if (contentType === 'json' && d.deleteReference && !d.keepReferece) {
|
|
68
70
|
delete data[key]
|
|
69
|
-
}
|
|
71
|
+
}
|
|
70
72
|
delete d.keepReferece
|
|
71
73
|
delete d.deleteReference
|
|
72
74
|
uniqRecords(d, csn.definitions[key])
|
|
@@ -74,13 +76,13 @@ const asJson = module.exports = async (data, csn, numRecords=1, options={}) => {
|
|
|
74
76
|
return data
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
function fillRecord(record, recordID,
|
|
79
|
+
function fillRecord(record, recordID, artifact, csn, data, parent, depth, referenceData) {
|
|
78
80
|
Object.defineProperty(record, '$id', {value: recordID, enumerable: false})
|
|
79
|
-
if (data[
|
|
81
|
+
if (data[artifact.name]) data[artifact.name].push(record) // add id to data, to be referenced by associations to prevent circular dependencies
|
|
80
82
|
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
record[el.name] = dataForField(el,
|
|
83
|
+
const elems = Object.values(artifact.elements ?? artifact.params).sort(asJson.elementsSorter(csn))
|
|
84
|
+
elems.forEach(el => {
|
|
85
|
+
record[el.name] = dataForField(el, artifact, csn, record, data, parent, depth, referenceData)
|
|
84
86
|
})
|
|
85
87
|
return record
|
|
86
88
|
}
|
|
@@ -110,7 +112,7 @@ asJson.elementsSorter = function (csn) {
|
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
|
|
113
|
-
function dataForField(element,
|
|
115
|
+
function dataForField(element, artifact, csn, record, data, parent, depth, referenceData) {
|
|
114
116
|
if (element.virtual || (element['@Core.Computed'] && element.value))
|
|
115
117
|
return
|
|
116
118
|
if (record[element.name] !== undefined)
|
|
@@ -119,12 +121,12 @@ function dataForField(element, entity, csn, record, data, parentEntity, depth, r
|
|
|
119
121
|
if (element.items?.elements) // `array of` / `many` with complex type
|
|
120
122
|
return [ fillRecord({}, record['$id'], element.items, csn, data, null, depth+1, referenceData) ]
|
|
121
123
|
else if (element.items?.type) // `array of` / `many` with simple type
|
|
122
|
-
return [ dataForField(element.items,
|
|
124
|
+
return [ dataForField(element.items, artifact, csn, record, data, null, depth+1, referenceData) ]
|
|
123
125
|
|
|
124
126
|
if (element.is_struct) {
|
|
125
127
|
let struct = {}
|
|
126
128
|
for (const el of element.elements) {
|
|
127
|
-
struct[el.name] = dataForField(el,
|
|
129
|
+
struct[el.name] = dataForField(el, artifact, csn, record, data, null, depth+1, referenceData)
|
|
128
130
|
}
|
|
129
131
|
return struct
|
|
130
132
|
}
|
|
@@ -166,11 +168,11 @@ function dataForField(element, entity, csn, record, data, parentEntity, depth, r
|
|
|
166
168
|
const enumName = randomFromList(Object.keys(element.enum))
|
|
167
169
|
const value = element.enum[enumName].val ?? element.enum[enumName].value ?? enumName
|
|
168
170
|
// key of common.CodeList, also fill name and descr with enum name
|
|
169
|
-
if (element.key &&
|
|
171
|
+
if (element.key && artifact.elements.name && artifact.elements.descr)
|
|
170
172
|
record.name = record.descr = enumName
|
|
171
173
|
return value
|
|
172
174
|
}
|
|
173
|
-
if (
|
|
175
|
+
if (artifact['@Communication.Contact.email']?.some(mail => mail.address?.['='] === element.name)
|
|
174
176
|
|| element['@Communication.IsEmailAddress']
|
|
175
177
|
|| element['@cds.on.insert']?.['='] === '$user'
|
|
176
178
|
|| element['@cds.on.update']?.['='] === '$user') {
|
|
@@ -180,9 +182,9 @@ function dataForField(element, entity, csn, record, data, parentEntity, depth, r
|
|
|
180
182
|
let str = `${element.name}-${record['$id']}`
|
|
181
183
|
|
|
182
184
|
if (element.key) {
|
|
183
|
-
str = `${
|
|
184
|
-
if (element.name === 'locale' &&
|
|
185
|
-
const record = randomFromReferenceData(
|
|
185
|
+
str = `${artifact.name.split('.').pop()}-${record['$id']}`
|
|
186
|
+
if (element.name === 'locale' && artifact.name.endsWith('texts')) {
|
|
187
|
+
const record = randomFromReferenceData(artifact, csn, referenceData)
|
|
186
188
|
if (record) return record.locale
|
|
187
189
|
return randomLanguage()
|
|
188
190
|
}
|
|
@@ -237,15 +239,15 @@ function dataForField(element, entity, csn, record, data, parentEntity, depth, r
|
|
|
237
239
|
}
|
|
238
240
|
case 'cds.Composition': {
|
|
239
241
|
const target = csn.definitions[element.target]
|
|
240
|
-
if (!target ||
|
|
241
|
-
|
|
242
|
+
if (!target || artifact.name === target.name || depth > MAX_ASSOC_DEPTH) return
|
|
243
|
+
|
|
242
244
|
let targetRecords = [] // in findKeysInOnCondition target records are linked to the entity, we don't want to overwrite any existing data
|
|
243
245
|
if (data[target.name]) data[target.name].deleteReference = true // for json composites references are nested and will be deleted when writing to file
|
|
244
|
-
|
|
246
|
+
|
|
245
247
|
const targetRecordCount = element.is2many ? 2 : 1 // TODO: make this configurable
|
|
246
248
|
const targetRecordID = randomNumber()
|
|
247
249
|
for (let i = 0; i < targetRecordCount; i++) {
|
|
248
|
-
const newRecord = fillRecord({}, targetRecordID+i, target, csn, data,
|
|
250
|
+
const newRecord = fillRecord({}, targetRecordID+i, target, csn, data, artifact, depth+1, referenceData)
|
|
249
251
|
if (newRecord) targetRecords.push(newRecord)
|
|
250
252
|
}
|
|
251
253
|
|
|
@@ -259,8 +261,8 @@ function dataForField(element, entity, csn, record, data, parentEntity, depth, r
|
|
|
259
261
|
}
|
|
260
262
|
uniqRecords(targetRecords, target)
|
|
261
263
|
|
|
262
|
-
//
|
|
263
|
-
return element.is2many ? targetRecords : targetRecords[0]
|
|
264
|
+
// consistent return data type: array
|
|
265
|
+
return element.is2many ? targetRecords : [targetRecords[0]]
|
|
264
266
|
}
|
|
265
267
|
case 'cds.Association': {
|
|
266
268
|
if (element.is2many)
|
|
@@ -280,16 +282,16 @@ function dataForField(element, entity, csn, record, data, parentEntity, depth, r
|
|
|
280
282
|
targetRecords = data[target.name] = []
|
|
281
283
|
else if (!targetRecords) // target entity is not in the list of entities to be created
|
|
282
284
|
return
|
|
283
|
-
|
|
285
|
+
|
|
284
286
|
if (targetRecords.length > 0) { // use record generated by following asociation
|
|
285
287
|
targetRecord = targetRecords.at(-1) // make sure the localized record references the newly generated texts
|
|
286
288
|
} else {
|
|
287
|
-
targetRecord = fillRecord({}, randomNumber(), target, csn, data,
|
|
289
|
+
targetRecord = fillRecord({}, randomNumber(), target, csn, data, artifact, depth+1, referenceData)
|
|
288
290
|
if (targetRecord) {
|
|
289
291
|
targetRecords.push(targetRecord)
|
|
290
292
|
uniqRecords(targetRecords, target)
|
|
291
293
|
}
|
|
292
|
-
if (
|
|
294
|
+
if (parent && (parent.name != artifact.name)) data[target.name].keepReferece = true
|
|
293
295
|
}
|
|
294
296
|
}
|
|
295
297
|
|
|
@@ -307,8 +309,10 @@ function dataForField(element, entity, csn, record, data, parentEntity, depth, r
|
|
|
307
309
|
return
|
|
308
310
|
case 'cds.Vector': // TODO add Vector
|
|
309
311
|
return
|
|
312
|
+
case 'cds.Map':
|
|
313
|
+
return {}
|
|
310
314
|
default: {
|
|
311
|
-
console.error(`Unknown type ${element._type} (${element.type}) in '${
|
|
315
|
+
console.error(`Unknown type ${element._type} (${element.type}) in '${artifact.name}:${element.name}'`)
|
|
312
316
|
return
|
|
313
317
|
}
|
|
314
318
|
}
|
|
@@ -393,14 +397,14 @@ function findKeysInOnCondition(assocElement, target) {
|
|
|
393
397
|
return [ownKey, ownKeyStructured, targetKey]
|
|
394
398
|
}
|
|
395
399
|
|
|
396
|
-
function uniqRecords(records,
|
|
397
|
-
const keys = Object.keys(
|
|
398
|
-
if (keys.length === 0)
|
|
400
|
+
function uniqRecords(records, artifact) {
|
|
401
|
+
const keys = Object.keys(artifact.keys||{})
|
|
402
|
+
if (keys.length === 0) keys.push('$id') // no keys (e.g. in actions) -> use record's $id
|
|
399
403
|
const keySet = new Set()
|
|
400
404
|
for (let i = 0; i < records.length; i++) {
|
|
401
405
|
const key = keys.map(k => records[i][k]).join('|')
|
|
402
406
|
if (keySet.has(key)) {
|
|
403
|
-
// console.debug('Removing', keys, key, 'from',
|
|
407
|
+
// console.debug('Removing', keys, key, 'from', artifact.name)
|
|
404
408
|
records.splice(i, 1)
|
|
405
409
|
i--
|
|
406
410
|
}
|
|
@@ -130,9 +130,12 @@ module.exports = class HttpTemplate extends require('../../plugin') {
|
|
|
130
130
|
for (const e of service.entities) {
|
|
131
131
|
payload[e.name] = []
|
|
132
132
|
}
|
|
133
|
+
for (const a of service.actions) {
|
|
134
|
+
payload[a.name] = []
|
|
135
|
+
}
|
|
133
136
|
}
|
|
134
137
|
// csn.all('entity').reduce((all, e) => { all[e.name] = []; return all }, {})
|
|
135
|
-
await asJson(payload, csn, 1)
|
|
138
|
+
await asJson(payload, csn, 1, {referenceData: []})
|
|
136
139
|
for (const p in payload) {
|
|
137
140
|
delete payload[p][0].texts
|
|
138
141
|
delete payload[p][0].localized
|
|
@@ -195,12 +198,8 @@ module.exports = class HttpTemplate extends require('../../plugin') {
|
|
|
195
198
|
|
|
196
199
|
let actionPayload = {}
|
|
197
200
|
if (a.params) {
|
|
198
|
-
for (const
|
|
199
|
-
actionPayload[
|
|
200
|
-
: value.type === 'string' ? 'sample string'
|
|
201
|
-
// TODO reference an object that has been generated
|
|
202
|
-
: typeof value.type === 'object' && value.type.ref ? payload[value.type.ref[0]][0][value.type.ref[1]]
|
|
203
|
-
: null
|
|
201
|
+
for (const pName of Object.keys(a.params)) {
|
|
202
|
+
actionPayload[pName] = payload[a.name]?.[0]?.[pName] ?? null
|
|
204
203
|
}
|
|
205
204
|
}
|
|
206
205
|
requests.push(`\n### ${simpleNameAction}\n# @name ${simpleNameAction}_POST\nPOST ${url}\n${headers}\n\n${JSON.stringify(actionPayload, null, 2)}\n`)
|
|
@@ -24,7 +24,6 @@ module.exports = class TyperTemplate extends require('../../plugin') {
|
|
|
24
24
|
let target = 'jsconfig.json'
|
|
25
25
|
|
|
26
26
|
if (exists('tsconfig.json')) {
|
|
27
|
-
source.compilerOptions.paths["#cds-models/*"][0] += '/index.ts'
|
|
28
27
|
target = 'tsconfig.json'
|
|
29
28
|
}
|
|
30
29
|
await merge(template('package.json')).into('package.json')
|
package/lib/mtx/push.js
CHANGED
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds-dk",
|
|
3
|
-
"version": "8.4.
|
|
3
|
+
"version": "8.4.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@sap/cds-dk",
|
|
9
|
-
"version": "8.4.
|
|
9
|
+
"version": "8.4.1",
|
|
10
10
|
"license": "SEE LICENSE IN LICENSE",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@cap-js/asyncapi": "^1.0.0",
|
|
@@ -64,11 +64,11 @@
|
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
66
|
"node_modules/@cap-js/sqlite": {
|
|
67
|
-
"version": "1.7.
|
|
67
|
+
"version": "1.7.6",
|
|
68
68
|
"license": "SEE LICENSE",
|
|
69
69
|
"optional": true,
|
|
70
70
|
"dependencies": {
|
|
71
|
-
"@cap-js/db-service": "^1.
|
|
71
|
+
"@cap-js/db-service": "^1.14.1",
|
|
72
72
|
"better-sqlite3": "^11.0.0"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
}
|
|
150
150
|
},
|
|
151
151
|
"node_modules/@eslint/js": {
|
|
152
|
-
"version": "9.
|
|
152
|
+
"version": "9.14.0",
|
|
153
153
|
"license": "MIT",
|
|
154
154
|
"engines": {
|
|
155
155
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
|
@@ -190,6 +190,17 @@
|
|
|
190
190
|
"node": ">=18.18.0"
|
|
191
191
|
}
|
|
192
192
|
},
|
|
193
|
+
"node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
|
|
194
|
+
"version": "0.3.1",
|
|
195
|
+
"license": "Apache-2.0",
|
|
196
|
+
"engines": {
|
|
197
|
+
"node": ">=18.18"
|
|
198
|
+
},
|
|
199
|
+
"funding": {
|
|
200
|
+
"type": "github",
|
|
201
|
+
"url": "https://github.com/sponsors/nzakas"
|
|
202
|
+
}
|
|
203
|
+
},
|
|
193
204
|
"node_modules/@humanwhocodes/module-importer": {
|
|
194
205
|
"version": "1.0.1",
|
|
195
206
|
"license": "Apache-2.0",
|
|
@@ -202,7 +213,7 @@
|
|
|
202
213
|
}
|
|
203
214
|
},
|
|
204
215
|
"node_modules/@humanwhocodes/retry": {
|
|
205
|
-
"version": "0.
|
|
216
|
+
"version": "0.4.1",
|
|
206
217
|
"license": "Apache-2.0",
|
|
207
218
|
"engines": {
|
|
208
219
|
"node": ">=18.18"
|
|
@@ -213,7 +224,7 @@
|
|
|
213
224
|
}
|
|
214
225
|
},
|
|
215
226
|
"node_modules/@sap/cds": {
|
|
216
|
-
"version": "8.4.
|
|
227
|
+
"version": "8.4.1",
|
|
217
228
|
"license": "SEE LICENSE IN LICENSE",
|
|
218
229
|
"dependencies": {
|
|
219
230
|
"@sap/cds-compiler": ">=5.1",
|
|
@@ -239,7 +250,7 @@
|
|
|
239
250
|
}
|
|
240
251
|
},
|
|
241
252
|
"node_modules/@sap/cds-compiler": {
|
|
242
|
-
"version": "5.4.
|
|
253
|
+
"version": "5.4.2",
|
|
243
254
|
"license": "SEE LICENSE IN LICENSE",
|
|
244
255
|
"dependencies": {
|
|
245
256
|
"antlr4": "4.9.3"
|
|
@@ -287,7 +298,7 @@
|
|
|
287
298
|
}
|
|
288
299
|
},
|
|
289
300
|
"node_modules/@sap/eslint-plugin-cds": {
|
|
290
|
-
"version": "3.1.
|
|
301
|
+
"version": "3.1.2",
|
|
291
302
|
"license": "See LICENSE file",
|
|
292
303
|
"dependencies": {
|
|
293
304
|
"@sap/cds": ">=7",
|
|
@@ -717,7 +728,7 @@
|
|
|
717
728
|
"license": "MIT"
|
|
718
729
|
},
|
|
719
730
|
"node_modules/cross-spawn": {
|
|
720
|
-
"version": "7.0.
|
|
731
|
+
"version": "7.0.4",
|
|
721
732
|
"license": "MIT",
|
|
722
733
|
"dependencies": {
|
|
723
734
|
"path-key": "^3.1.0",
|
|
@@ -875,19 +886,19 @@
|
|
|
875
886
|
}
|
|
876
887
|
},
|
|
877
888
|
"node_modules/eslint": {
|
|
878
|
-
"version": "9.
|
|
889
|
+
"version": "9.14.0",
|
|
879
890
|
"license": "MIT",
|
|
880
891
|
"dependencies": {
|
|
881
892
|
"@eslint-community/eslint-utils": "^4.2.0",
|
|
882
|
-
"@eslint-community/regexpp": "^4.
|
|
893
|
+
"@eslint-community/regexpp": "^4.12.1",
|
|
883
894
|
"@eslint/config-array": "^0.18.0",
|
|
884
895
|
"@eslint/core": "^0.7.0",
|
|
885
896
|
"@eslint/eslintrc": "^3.1.0",
|
|
886
|
-
"@eslint/js": "9.
|
|
897
|
+
"@eslint/js": "9.14.0",
|
|
887
898
|
"@eslint/plugin-kit": "^0.2.0",
|
|
888
|
-
"@humanfs/node": "^0.16.
|
|
899
|
+
"@humanfs/node": "^0.16.6",
|
|
889
900
|
"@humanwhocodes/module-importer": "^1.0.1",
|
|
890
|
-
"@humanwhocodes/retry": "^0.
|
|
901
|
+
"@humanwhocodes/retry": "^0.4.0",
|
|
891
902
|
"@types/estree": "^1.0.6",
|
|
892
903
|
"@types/json-schema": "^7.0.15",
|
|
893
904
|
"ajv": "^6.12.4",
|
|
@@ -895,9 +906,9 @@
|
|
|
895
906
|
"cross-spawn": "^7.0.2",
|
|
896
907
|
"debug": "^4.3.2",
|
|
897
908
|
"escape-string-regexp": "^4.0.0",
|
|
898
|
-
"eslint-scope": "^8.
|
|
899
|
-
"eslint-visitor-keys": "^4.
|
|
900
|
-
"espree": "^10.
|
|
909
|
+
"eslint-scope": "^8.2.0",
|
|
910
|
+
"eslint-visitor-keys": "^4.2.0",
|
|
911
|
+
"espree": "^10.3.0",
|
|
901
912
|
"esquery": "^1.5.0",
|
|
902
913
|
"esutils": "^2.0.2",
|
|
903
914
|
"fast-deep-equal": "^3.1.3",
|
|
@@ -933,7 +944,7 @@
|
|
|
933
944
|
}
|
|
934
945
|
},
|
|
935
946
|
"node_modules/eslint-scope": {
|
|
936
|
-
"version": "8.
|
|
947
|
+
"version": "8.2.0",
|
|
937
948
|
"license": "BSD-2-Clause",
|
|
938
949
|
"dependencies": {
|
|
939
950
|
"esrecurse": "^4.3.0",
|
|
@@ -947,7 +958,7 @@
|
|
|
947
958
|
}
|
|
948
959
|
},
|
|
949
960
|
"node_modules/eslint-visitor-keys": {
|
|
950
|
-
"version": "4.
|
|
961
|
+
"version": "4.2.0",
|
|
951
962
|
"license": "Apache-2.0",
|
|
952
963
|
"engines": {
|
|
953
964
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
|
@@ -957,12 +968,12 @@
|
|
|
957
968
|
}
|
|
958
969
|
},
|
|
959
970
|
"node_modules/espree": {
|
|
960
|
-
"version": "10.
|
|
971
|
+
"version": "10.3.0",
|
|
961
972
|
"license": "BSD-2-Clause",
|
|
962
973
|
"dependencies": {
|
|
963
|
-
"acorn": "^8.
|
|
974
|
+
"acorn": "^8.14.0",
|
|
964
975
|
"acorn-jsx": "^5.3.2",
|
|
965
|
-
"eslint-visitor-keys": "^4.
|
|
976
|
+
"eslint-visitor-keys": "^4.2.0"
|
|
966
977
|
},
|
|
967
978
|
"engines": {
|
|
968
979
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|