@sap/cds-dk 7.7.2 → 7.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/add.js +40 -17
- package/bin/bind.js +21 -12
- package/bin/build.js +14 -2
- package/bin/cds.js +8 -0
- package/bin/deploy/to-sqlite.js +1 -1
- package/bin/help.js +4 -1
- package/bin/version.js +54 -22
- package/lib/bind/userCdsrcJsonStorageProvider.js +1 -1
- package/lib/build/buildTaskFactory.js +2 -2
- package/lib/build/provider/hana/index.js +1 -1
- package/lib/build/provider/internalBuildTaskProvider.js +23 -11
- package/lib/build/provider/mtx/resourcesTarBuilder.js +16 -4
- package/lib/build/provider/mtx-extension/extensionCompilation.js +13 -2
- package/lib/build/provider/mtx-extension/index.js +6 -2
- package/lib/build/provider/nodejs/index.js +4 -4
- package/lib/build/util.js +7 -18
- package/lib/compile/openapi/index.js +2 -1
- package/lib/index.js +2 -2
- package/lib/init/constants.js +3 -2
- package/lib/init/index.js +9 -23
- package/lib/init/plugin.js +8 -0
- package/lib/init/registries/mta.js +6 -0
- package/lib/init/template/data/as-csv.js +60 -0
- package/lib/init/template/data/as-json.js +441 -0
- package/lib/init/template/data/index.js +116 -86
- package/lib/init/template/enterprise-messaging/files/package.json.hbs +2 -1
- package/lib/init/template/enterprise-messaging/index.js +16 -3
- package/lib/init/template/enterprise-messaging-shared/files/package.json.hbs +2 -1
- package/lib/init/template/enterprise-messaging-shared/index.js +16 -3
- package/lib/init/template/helm/files/content-deployment/Chart.yaml +1 -1
- package/lib/init/template/helm/files/content-deployment/templates/job.yaml +7 -0
- package/lib/init/template/helm/files/content-deployment/values.schema.json +17 -0
- package/lib/init/template/helm/files/content-deployment/values.yaml +6 -1
- package/lib/init/template/helm/files/service-instance/Chart.yaml +1 -1
- package/lib/init/template/helm/files/service-instance/templates/serviceinstance.yaml +4 -0
- package/lib/init/template/helm/files/service-instance/values.schema.json +17 -1
- package/lib/init/template/helm/files/service-instance/values.yaml +4 -1
- package/lib/init/template/helm/files/values.yaml.hbs +2 -2
- package/lib/init/template/helm/files/web-application/Chart.yaml +1 -1
- package/lib/init/template/helm/files/web-application/templates/api-rule.yaml +4 -0
- package/lib/init/template/helm/files/web-application/templates/network-policy.yaml +15 -0
- package/lib/init/template/helm/files/web-application/values.schema.json +41 -0
- package/lib/init/template/helm/files/web-application/values.yaml +19 -1
- package/lib/init/template/helm/index.js +5 -2
- package/lib/init/template/helm-unified-runtime/files/values.yaml.hbs +2 -2
- package/lib/init/template/helm-unified-runtime/index.js +13 -7
- package/lib/init/template/html5-repo/index.js +0 -1
- package/lib/init/template/http/index.js +182 -0
- package/lib/init/template/kafka/files/mta.yaml.hbs +14 -0
- package/lib/init/template/kafka/files/package.json.hbs +22 -0
- package/lib/init/template/kafka/files/values.yaml +10 -0
- package/lib/init/template/kafka/index.js +39 -0
- package/lib/init/template/lint/index.js +4 -4
- package/lib/init/template/mta/files/mta.yaml.hbs +4 -0
- package/lib/init/template/multitenancy/files/values.yaml.hbs +2 -2
- package/lib/util/cf.js +6 -2
- package/npm-shrinkwrap.json +269 -293
- package/package.json +1 -1
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
const { randomUUID } = require('node:crypto')
|
|
2
|
+
const { dirname } = require('node:path')
|
|
3
|
+
const cds = require('../../../cds')
|
|
4
|
+
|
|
5
|
+
let counter = Math.round(Math.random() * 10000) // Default random seed // Non-deterministic behaviour
|
|
6
|
+
const MAX_ASSOC_DEPTH = 1
|
|
7
|
+
|
|
8
|
+
module.exports = async (data, csn, numRecords=1, existingData) => {
|
|
9
|
+
// if no entity restrictions are given, use all entities
|
|
10
|
+
if (!data) data = csn.all('entity').reduce((all, e) => { all[e.name] = []; return all }, {})
|
|
11
|
+
|
|
12
|
+
if (!existingData)
|
|
13
|
+
existingData = await cds.deploy.resources(csn)
|
|
14
|
+
|
|
15
|
+
addSemantics(csn)
|
|
16
|
+
|
|
17
|
+
// sort all entities first that are referenced by others, so that dependant entities can be created after them
|
|
18
|
+
const { definitions: defs } = csn
|
|
19
|
+
const entityNames = Object.keys(data).sort((n1, n2) => {
|
|
20
|
+
const n1HasComp = Object.values(defs[n1].elements).some(el => el.type === 'cds.Composition')
|
|
21
|
+
const n2HasComp = Object.values(defs[n2].elements).some(el => el.type === 'cds.Composition')
|
|
22
|
+
if (n1HasComp && !n2HasComp) return -1
|
|
23
|
+
if (!n1HasComp && n2HasComp) return 1
|
|
24
|
+
return 0
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
for (const name of entityNames) {
|
|
28
|
+
if (!data[name] // removed on purpose because records are created inline through compositions
|
|
29
|
+
|| data[name].length >= numRecords) { // already created by following associations
|
|
30
|
+
continue
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const records = data[name] = Array(numRecords).fill({}).map(() => ({}))
|
|
34
|
+
const entity = defs[name]
|
|
35
|
+
|
|
36
|
+
const idNum = randomNumber()
|
|
37
|
+
for (let i = 0; i < numRecords; i++) {
|
|
38
|
+
fillRecord(records[i], idNum+i, entity, csn, data, null, 0, existingData)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return data
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function fillRecord(record, recordID, entity, csn, data, parentEntity, depth, existingData) {
|
|
45
|
+
Object.defineProperty(record, '$id', {value: recordID, enumerable: false})
|
|
46
|
+
|
|
47
|
+
const { elements } = entity
|
|
48
|
+
|
|
49
|
+
// fill in the keys first, to have them ready for foreign key refs
|
|
50
|
+
const keys = Object.keys(entity.keys||{})
|
|
51
|
+
for (const key of keys) {
|
|
52
|
+
record[key] = dataForField(elements[key], entity, csn, record, data, parentEntity, depth, existingData)
|
|
53
|
+
}
|
|
54
|
+
// non-key elements
|
|
55
|
+
Object.keys(elements).filter(name => !keys.includes(name)).forEach(name => {
|
|
56
|
+
record[name] = dataForField(elements[name], entity, csn, record, data, parentEntity, depth, existingData)
|
|
57
|
+
})
|
|
58
|
+
return record
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function dataForField(element, entity, csn, record, data, parentEntity, depth, existingData) {
|
|
62
|
+
if (element.virtual || element['@Core.Computed'])
|
|
63
|
+
return
|
|
64
|
+
switch (element._type) {
|
|
65
|
+
case 'cds.Time':
|
|
66
|
+
return '22:59:53'
|
|
67
|
+
|
|
68
|
+
case 'cds.Timestamp': {
|
|
69
|
+
const range = {from: new Date('2000-01-01'), to: new Date('2024-01-01')}
|
|
70
|
+
return randomDateInRange(range).toISOString()
|
|
71
|
+
}
|
|
72
|
+
case 'cds.Date': {
|
|
73
|
+
const range = {from: new Date('2000-01-01'), to: new Date('2024-01-01')}
|
|
74
|
+
annotatedDateRange(element, range)
|
|
75
|
+
const generatedDate = randomDateInRange(range)
|
|
76
|
+
const date = generatedDate.toISOString().split('T', 1)[0]
|
|
77
|
+
return date
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
case 'cds.DateTime': {
|
|
81
|
+
const range = {from: new Date('2000-01-01'), to: new Date('2024-01-01')}
|
|
82
|
+
annotatedDateRange(element, range)
|
|
83
|
+
const generatedDate = randomDateInRange(range)
|
|
84
|
+
return generatedDate.toISOString()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
case 'cds.LargeString':
|
|
88
|
+
case 'cds.String': {
|
|
89
|
+
if (element['@data.gen']?.startsWith('faker.')) {
|
|
90
|
+
return fakerValue(element['@data.gen'], csn)
|
|
91
|
+
}
|
|
92
|
+
if (element.enum) {
|
|
93
|
+
const enums = Object.entries(element.enum).map(([name, val]) => val.value ?? name)
|
|
94
|
+
return randomFromList(enums)
|
|
95
|
+
}
|
|
96
|
+
if (entity['@Communication.Contact.email']?.some(mail => mail.address?.['='] === element.name)
|
|
97
|
+
|| element['@Communication.IsEmailAddress']
|
|
98
|
+
|| element['@cds.on.insert']?.['='] === '$user'
|
|
99
|
+
|| element['@cds.on.update']?.['='] === '$user') {
|
|
100
|
+
return randomEmailAddress(element.name, record['$id'], csn)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let str = `${element.name}-${record['$id']}`
|
|
104
|
+
|
|
105
|
+
if (element.key) {
|
|
106
|
+
str = `${entity.name.split('.').pop()}-${record['$id']}`
|
|
107
|
+
if (element.name === 'locale' && entity.name.endsWith('texts')) {
|
|
108
|
+
const record = randomFromExistingData(entity, csn, existingData)
|
|
109
|
+
if (record) return record.locale
|
|
110
|
+
return randomLanguage()
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (element['@assert.format']) {
|
|
114
|
+
str = randomStrForRegex(element['@assert.format'], csn)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (element.length && str.length > element.length) {
|
|
118
|
+
str = str.slice(-element.length) // truncate to length
|
|
119
|
+
}
|
|
120
|
+
return str
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
case 'cds.UUID': {
|
|
124
|
+
// create a pseudo-random UUID based on the record ID
|
|
125
|
+
const idStr = '' + record['$id']
|
|
126
|
+
return idStr + randomUUID().slice(idStr.length)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
case 'cds.Boolean':
|
|
130
|
+
return (record['$id'].toString(2).at(-1)) < 0.5
|
|
131
|
+
|
|
132
|
+
case 'cds.Int16':
|
|
133
|
+
case 'cds.Integer64':
|
|
134
|
+
case 'cds.Integer': {
|
|
135
|
+
if (element.key)
|
|
136
|
+
return record['$id']
|
|
137
|
+
if (element.enum) {
|
|
138
|
+
const enums = Object.values(element.enum).map((value) => value.val)
|
|
139
|
+
return parseInt(randomFromList(enums))
|
|
140
|
+
}
|
|
141
|
+
const range = {from: 0, to: 100}
|
|
142
|
+
annotatedIntRange(element, range)
|
|
143
|
+
return randomIntInRange(range)
|
|
144
|
+
}
|
|
145
|
+
case 'cds.Decimal': {
|
|
146
|
+
const range = {from: 0, to: 100}
|
|
147
|
+
annotatedIntRange(element, range)
|
|
148
|
+
return +(randomDecimalInRange(range).toFixed(element.scale ?? 2))
|
|
149
|
+
}
|
|
150
|
+
case 'cds.Double': {
|
|
151
|
+
const range = {from: 0, to: 100}
|
|
152
|
+
return randomDecimalInRange(range)
|
|
153
|
+
}
|
|
154
|
+
case 'cds.Composition': {
|
|
155
|
+
const target = csn.definitions[element.target]
|
|
156
|
+
if (!target)
|
|
157
|
+
return
|
|
158
|
+
delete data[target.name] // as data for the target is created inline below, no need to have it separately
|
|
159
|
+
if (target === parentEntity || depth > MAX_ASSOC_DEPTH)
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
const targetRecords = []
|
|
163
|
+
const targetRecordCount = element.is2many ? 2 : 1 // TODO: make this configurable
|
|
164
|
+
const targetRecordID = randomNumber()
|
|
165
|
+
for (let i = 0; i < targetRecordCount; i++) {
|
|
166
|
+
targetRecords.push(fillRecord({}, targetRecordID+i, target, csn, data, entity, depth+1, existingData))
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const [ownKey, ownKeyStructured, targetKey] = findKeysInOnCondition(element, target)
|
|
170
|
+
if (ownKey && targetKey) {
|
|
171
|
+
const value = ownKeyStructured ? { [ownKey]: record[ownKey] } : record[ownKey]
|
|
172
|
+
targetRecords.forEach(targetRecord => {
|
|
173
|
+
// Set parent in target as non-enumerable. Json serialization doesn't need it, but csv does.
|
|
174
|
+
Object.defineProperty(targetRecord, targetKey, { value, enumerable: false })
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// set the entire target record inline
|
|
179
|
+
return element.is2many ? targetRecords : targetRecords[0]
|
|
180
|
+
}
|
|
181
|
+
case 'cds.Association': {
|
|
182
|
+
if (element.is2many)
|
|
183
|
+
return
|
|
184
|
+
const target = csn.definitions[element.target]
|
|
185
|
+
if (!target || target === parentEntity || depth > MAX_ASSOC_DEPTH)
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
let targetRecord = randomFromExistingData(target, csn, existingData)
|
|
189
|
+
if (targetRecord) {
|
|
190
|
+
delete data[target.name] // indicate that existing data is used and no new data shall be created
|
|
191
|
+
delete data[target.name + '.texts'] // also delete associated texts entity
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
let targetRecords = data[target.name]
|
|
195
|
+
if (!targetRecords && (element['@mandatory']) || element['@assert.target'])
|
|
196
|
+
targetRecords = data[target.name] = []
|
|
197
|
+
else if (!targetRecords) // target entity is not in the list of entities to be created
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
targetRecord = fillRecord({}, randomNumber(), target, csn, data, entity, depth, existingData)
|
|
201
|
+
targetRecords.push(targetRecord)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// TODO: handle foreign keys correctly
|
|
205
|
+
const assoc = {}
|
|
206
|
+
Object.keys(target.elements)
|
|
207
|
+
.filter(keyName => target.elements[keyName].key)
|
|
208
|
+
.forEach(keyName => assoc[keyName] = targetRecord[keyName])
|
|
209
|
+
return assoc
|
|
210
|
+
}
|
|
211
|
+
default: {
|
|
212
|
+
throw new Error(`Unknown type '${element.type}' in '${entity.name}:${element.name}'`)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function randomFromExistingData (entity, csn, existingData) {
|
|
218
|
+
const files = Object.entries(existingData)
|
|
219
|
+
.filter(([, name]) => matchesEntityOrProjection(name, entity, csn))
|
|
220
|
+
.map(([file]) => file)
|
|
221
|
+
if (files.length === 0) return
|
|
222
|
+
|
|
223
|
+
if (files[0].endsWith('.csv')) {
|
|
224
|
+
const content = cds.utils.fs.readFileSync(files[0]).toString()
|
|
225
|
+
const records = cds.parse.csv(content)
|
|
226
|
+
if (records?.length > 1) {
|
|
227
|
+
const header = records[0] // 0 is header line
|
|
228
|
+
const randomRecord = records.length === 2 ? records[1] : records[randomIntInRange({from: 1, to: records.length-1} )]
|
|
229
|
+
let result = {}
|
|
230
|
+
for (let i = 0; i < records[0].length; i++) {
|
|
231
|
+
result[header[i]] = randomRecord[i]
|
|
232
|
+
}
|
|
233
|
+
return result
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
let content
|
|
238
|
+
if (files[0].endsWith('.json')) {
|
|
239
|
+
content = cds.utils.fs.readFileSync(files[0]).toString()
|
|
240
|
+
} else {
|
|
241
|
+
content = files[0] // for tests, assuming the content is given inline
|
|
242
|
+
}
|
|
243
|
+
if (content) {
|
|
244
|
+
const records = JSON.parse(content)
|
|
245
|
+
if (records.length === 1)
|
|
246
|
+
return records[0]
|
|
247
|
+
else if (records.length > 1)
|
|
248
|
+
return records[randomIntInRange({from: 0, to: records.length-1} )]
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* whether the given name matches the entity or one of its projections */
|
|
254
|
+
function matchesEntityOrProjection(name, entity, csn) {
|
|
255
|
+
if (name === entity?.name) return true
|
|
256
|
+
// follow simle projections
|
|
257
|
+
const p = entity?.query?.SELECT || entity?.projection
|
|
258
|
+
if (p?.from?.ref?.length === 1) {
|
|
259
|
+
if (p.from.ref[0] === name) return true
|
|
260
|
+
return matchesEntityOrProjection(name, csn.definitions[p.from.ref[0]], csn)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function findKeysInOnCondition(assocElement, target) {
|
|
265
|
+
const { on } = assocElement
|
|
266
|
+
let ownKey, targetKey, ownKeyStructured
|
|
267
|
+
if (on?.length === 3 && on[1] === '=') {
|
|
268
|
+
for (let { ref } of [on[0], on[2]]) {
|
|
269
|
+
if (ref.length > 1 && ref[0] === assocElement.name && target.elements[ref[1]]) {
|
|
270
|
+
targetKey = ref[1]
|
|
271
|
+
}
|
|
272
|
+
else if (ref[0] === '$self') {
|
|
273
|
+
ownKey = Object.keys(assocElement.parent.keys)[0] // TODO composite keys
|
|
274
|
+
ownKeyStructured = true
|
|
275
|
+
}
|
|
276
|
+
else if (assocElement.parent.elements[ref[0]]) {
|
|
277
|
+
ownKey = ref[0]
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return [ownKey, ownKeyStructured, targetKey]
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function annotatedDateRange(el, dateRange) {
|
|
285
|
+
if (el['@assert.range']?.length === 2) {
|
|
286
|
+
const dateFrom = new Date(el['@assert.range'][0])
|
|
287
|
+
const dateTo = new Date(el['@assert.range'][1])
|
|
288
|
+
if (dateRange.from < dateFrom) {
|
|
289
|
+
dateRange.from = dateFrom
|
|
290
|
+
}
|
|
291
|
+
if (dateRange.to > dateTo) {
|
|
292
|
+
dateRange.to = dateTo
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function annotatedIntRange(el, intRange) {
|
|
298
|
+
if (el['@assert.range']?.length === 2) {
|
|
299
|
+
const rangeFrom = el['@assert.range'][0]
|
|
300
|
+
const rangeTo = el['@assert.range'][1]
|
|
301
|
+
if (Number.isInteger(rangeFrom) && intRange.from < rangeFrom) {
|
|
302
|
+
intRange.from = rangeFrom
|
|
303
|
+
}
|
|
304
|
+
if (Number.isInteger(rangeTo) && intRange.to > rangeTo) {
|
|
305
|
+
intRange.to = rangeTo
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
//TODO : decimalRange
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const langList = ['ar', 'cs', 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja', 'ko', 'ms', 'nl', 'pl', 'pt', 'ro', 'ru', 'sv', 'th', 'tr', 'zh']
|
|
312
|
+
function randomLanguage() {
|
|
313
|
+
return randomFromList(langList)
|
|
314
|
+
|
|
315
|
+
}
|
|
316
|
+
function randomFromList(list) {
|
|
317
|
+
return list[randomNumber() % list.length]
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function randomEmailAddress(firstName, num, csn) {
|
|
321
|
+
let addr = fakerValue('faker.internet.email', csn, false)
|
|
322
|
+
if (addr) return addr
|
|
323
|
+
const provider = randomFromList(['example.com', 'example.org', 'example.net'])
|
|
324
|
+
return `${firstName}.${num.toString(36)}@${provider}`
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function randomStrForRegex(regexStr, csn) {
|
|
328
|
+
// TODO make 'randexp' a hard dependency when this code is in a plugin
|
|
329
|
+
const randexp = requirePackage('randexp', csn, regexStr)
|
|
330
|
+
if (!randexp) return `Install package 'randexp' to create data for regex ${regexStr}`
|
|
331
|
+
return randexp.randexp(regexStr)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function fakerValue(expr, csn, useFallback=true, ...args) {
|
|
335
|
+
// `en` bundle is way smaller than full faker. TODO use different Faker locales?
|
|
336
|
+
const Faker = requirePackage('@faker-js/faker/locale/en', csn)
|
|
337
|
+
if (!Faker && !useFallback) return
|
|
338
|
+
if (!Faker) return `Install package '@faker-js/faker' to create data for ${expr}`
|
|
339
|
+
|
|
340
|
+
let resultPart
|
|
341
|
+
let [, category, func] = expr.split('.')
|
|
342
|
+
const cat = Faker.faker[category]
|
|
343
|
+
if (typeof cat !== 'object') return `Unknown faker category ${category} in ${expr}`
|
|
344
|
+
const funcParts = func.split('::')
|
|
345
|
+
if (funcParts.length === 2) {
|
|
346
|
+
resultPart = funcParts.pop()
|
|
347
|
+
func = funcParts.pop()
|
|
348
|
+
}
|
|
349
|
+
const fn = cat[func]
|
|
350
|
+
if (typeof fn !== 'function') return `Unknown faker function ${func} in ${expr}`
|
|
351
|
+
if (category === 'internet' && func === 'email') {
|
|
352
|
+
// only allow domains that cannot be registered
|
|
353
|
+
const provider = randomFromList(['example.com', 'example.org', 'example.net'])
|
|
354
|
+
return fn({ provider })
|
|
355
|
+
}
|
|
356
|
+
let res = args ? fn(...args) : fn()
|
|
357
|
+
if (res && resultPart) {
|
|
358
|
+
return res[resultPart]
|
|
359
|
+
}
|
|
360
|
+
return res
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const packages = {}
|
|
364
|
+
function requirePackage(id, csn) {
|
|
365
|
+
try {
|
|
366
|
+
if (!packages[id]) {
|
|
367
|
+
const pkg = resolvePackage(id, csn)
|
|
368
|
+
if (pkg)
|
|
369
|
+
packages[id] = require(pkg)
|
|
370
|
+
}
|
|
371
|
+
return packages[id]
|
|
372
|
+
} catch (err) {
|
|
373
|
+
if (err.code !== 'MODULE_NOT_FOUND') throw err
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function resolvePackage(id, csn) {
|
|
378
|
+
try {
|
|
379
|
+
return require.resolve(id, { paths: [...csn.$sources.map(dirname), cds.root, __dirname] })
|
|
380
|
+
} catch (err) {
|
|
381
|
+
if (err.code !== 'MODULE_NOT_FOUND') throw err
|
|
382
|
+
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// add semantics to well-known entities
|
|
387
|
+
function addSemantics(csn) {
|
|
388
|
+
if (resolvePackage('@faker-js/faker', csn)) { // only if faker is installed
|
|
389
|
+
csn.all('entity').forEach(entity => {
|
|
390
|
+
const { elements } = entity
|
|
391
|
+
if (entity.name === 'sap.common.Countries') {
|
|
392
|
+
elements.code['@data.gen'] = 'faker.location.countryCode'
|
|
393
|
+
elements.name['@data.gen'] = elements.descr['@data.gen'] = 'faker.location.country'
|
|
394
|
+
}
|
|
395
|
+
else if (entity.name === 'sap.common.Currencies') {
|
|
396
|
+
elements.code['@data.gen'] = 'faker.finance.currency::code'
|
|
397
|
+
elements.name['@data.gen'] = elements.descr['@data.gen'] = 'faker.finance.currency::name'
|
|
398
|
+
elements.symbol['@data.gen'] = 'faker.finance.currency::symbol'
|
|
399
|
+
}
|
|
400
|
+
else if (entity.name === 'sap.common.Timezones') {
|
|
401
|
+
elements.code['@data.gen'] = 'faker.location.timeZone'
|
|
402
|
+
elements.name['@data.gen'] = elements.descr['@data.gen'] = 'faker.location.timeZone'
|
|
403
|
+
}
|
|
404
|
+
})
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function randomDateInRange(dateRange) {
|
|
409
|
+
const dateFrom = dateRange.from
|
|
410
|
+
const returnDate = new Date(dateFrom.getUTCFullYear(), dateFrom.getUTCMonth(), dateFrom.getUTCDate())//needed to avoid overwriting the original dateFrom when generating a random Date
|
|
411
|
+
const diffTime = dateRange.to.valueOf() - dateRange.from.valueOf();
|
|
412
|
+
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
413
|
+
const step = randomNumber() % diffDays
|
|
414
|
+
returnDate.setUTCDate(returnDate.getUTCDate() + step);
|
|
415
|
+
return returnDate
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function randomDecimalInRange(decimalRange) {
|
|
419
|
+
const max = decimalRange.to
|
|
420
|
+
const min = decimalRange.from
|
|
421
|
+
const num = min + (randomNumber() % (max - min))
|
|
422
|
+
let dec = randomNumber()
|
|
423
|
+
const divider = Math.pow(10, dec.toString().length)
|
|
424
|
+
dec = dec / divider
|
|
425
|
+
const returnValue = num + dec
|
|
426
|
+
return returnValue
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function randomIntInRange(intRange) {
|
|
430
|
+
const max = intRange.to
|
|
431
|
+
const min = intRange.from
|
|
432
|
+
return min + (randomNumber() % max)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const p = 5651;
|
|
436
|
+
const q = 5623;
|
|
437
|
+
const M = p * q;
|
|
438
|
+
function randomNumber() {
|
|
439
|
+
counter = (counter * counter) % M
|
|
440
|
+
return counter;
|
|
441
|
+
}
|