@sap/cds-dk 9.3.2 → 9.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 +41 -0
- package/bin/bind.js +1 -1
- package/bin/cds.js +3 -0
- package/bin/compile/index.js +53 -3
- package/bin/data-products/get-csn-via-dp-cli.js +41 -0
- package/bin/data-products/get-csn.js +76 -0
- package/bin/data-products/import.js +114 -0
- package/bin/data-products/index.js +30 -0
- package/bin/data-products/merge-csns.js +46 -0
- package/bin/data-products/prepare-csn.js +282 -0
- package/bin/data-products/prepare-files.js +69 -0
- package/bin/export.js +240 -0
- package/bin/help/export.md +37 -0
- package/bin/help/import.md +11 -0
- package/bin/help/minify.md +23 -0
- package/bin/import.js +13 -3
- package/bin/inspect.js +226 -0
- package/bin/minify.js +123 -0
- package/bin/util/cli.js +132 -0
- package/bin/util/log.js +2 -0
- package/bin/version.js +2 -1
- package/bin/watch.js +21 -3
- package/lib/bind/shared.js +3 -1
- package/lib/build/buildTaskEngine.js +2 -1
- package/lib/build/plugins/helm/files/templates/NOTES.txt +1 -1
- package/lib/build/plugins/helm/index.js +1 -1
- package/lib/build/provider/internalBuildTaskProvider.js +3 -3
- package/lib/build/provider/nodejs/index.js +62 -3
- package/lib/deploy/index.js +1 -1
- package/lib/deploy/to-hana/hana.js +18 -16
- package/lib/import/odata/v2parser.js +2 -2
- package/lib/import/odata/v4parser.js +21 -2
- package/lib/import/odata.package.json +4 -4
- package/lib/init/constants.js +2 -1
- package/lib/init/index.js +2 -0
- package/lib/init/mvn.js +3 -2
- package/lib/init/projectReader.js +1 -0
- package/lib/init/registries/mta.js +6 -0
- package/lib/init/template/ams/files/manifest.yml.hbs +1 -1
- package/lib/init/template/ams/files/mta.yaml.hbs +1 -1
- package/lib/init/template/app-frontend/files/app-package.json.hbs +20 -0
- package/lib/init/template/app-frontend/files/index.html.hbs +34 -0
- package/lib/init/template/app-frontend/files/manifest.json +13 -0
- package/lib/init/template/app-frontend/files/mta.yaml.hbs +71 -0
- package/lib/init/template/app-frontend/files/package.json.hbs +17 -0
- package/lib/init/template/app-frontend/files/ui5-deploy.yaml.hbs +25 -0
- package/lib/init/template/app-frontend/files/ui5.yaml.hbs +6 -0
- package/lib/init/template/app-frontend/files/values.yaml.hbs +20 -0
- package/lib/init/template/app-frontend/files/xs-app.json.hbs +17 -0
- package/lib/init/template/app-frontend/index.js +98 -0
- package/lib/init/template/approuter/files/mta.yaml.hbs +0 -2
- package/lib/init/template/attachments/index.js +4 -10
- package/lib/init/template/audit-logging/files/package.json.hbs +5 -0
- package/lib/init/template/handler/index.js +1 -0
- package/lib/init/template/helm/index.js +5 -1
- package/lib/init/template/html5-repo/files/mta.yaml.hbs +7 -2
- package/lib/init/template/ias/files/manifest.yml.hbs +1 -1
- package/lib/init/template/ias/files/mta.yaml.hbs +6 -5
- package/lib/init/template/ias/files/values.yaml.hbs +10 -1
- package/lib/init/template/multitenancy/files/mta.yaml.hbs +16 -3
- package/lib/init/template/multitenancy/files/package.json.hbs +5 -0
- package/lib/init/template/multitenancy/files/values.yaml.hbs +1 -6
- package/lib/init/template/portal/files/mta.yaml.hbs +12 -2
- package/lib/init/template/sample/files/db/schema.cds +5 -8
- package/lib/init/template/typer/files/xsconfig.json +2 -1
- package/lib/init/template/typescript/files/package.json +1 -1
- package/lib/init/template/typescript/files/tsconfig.json +4 -1
- package/lib/init/template/workzone-standard/files/mta.yaml.hbs +4 -0
- package/lib/init/template/workzone-standard/index.js +3 -2
- package/lib/init/template/xsuaa/files/package.json.hbs +3 -0
- package/lib/lint/eslintHelp.json +1 -1
- package/lib/util/term.js +2 -4
- package/npm-shrinkwrap.json +25 -25
- package/package.json +1 -1
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
//
|
|
2
|
+
// input: single (interop) CSN for a Data Product, including texts
|
|
3
|
+
// to be clarified: should/does it already contain a service wrapper?
|
|
4
|
+
// service name (incl. namespace) for step 5
|
|
5
|
+
// output: single CSN file with ...
|
|
6
|
+
// 1) all type refs resolved to built-in types
|
|
7
|
+
// 2) all type definitions removed
|
|
8
|
+
// 3) unwanted annotations removed, some annotations renamed
|
|
9
|
+
// 4) unused texts removed
|
|
10
|
+
// 5) wrap everything into service if not yet done, service annos added
|
|
11
|
+
// 6) all annotations for entities/elements moved from "definitions" into "extensions"
|
|
12
|
+
// 7) add java name annotations
|
|
13
|
+
//
|
|
14
|
+
// Assumptions on input CSN (reflecting current possibilities of interop CSN):
|
|
15
|
+
// - definitions section only contains
|
|
16
|
+
// - zero or one service
|
|
17
|
+
// - entities
|
|
18
|
+
// - if there is a service, all entities are part of this service
|
|
19
|
+
// - no views/projections
|
|
20
|
+
// - no actions/functions
|
|
21
|
+
// - no parameters
|
|
22
|
+
// - type definitions
|
|
23
|
+
// - type definitions are only simple (scalar) types referring to built-in types
|
|
24
|
+
// - no structs, no arrays, no assocs
|
|
25
|
+
// - can be enum type
|
|
26
|
+
// - no type chains
|
|
27
|
+
// - entity elements can be
|
|
28
|
+
// - built-in types, including enum types
|
|
29
|
+
// - references to the simple (scalar) type definitions described above
|
|
30
|
+
// - unmanaged(!) associations
|
|
31
|
+
// - no structs, no arrays
|
|
32
|
+
|
|
33
|
+
'use strict'
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
*
|
|
37
|
+
* @param csn
|
|
38
|
+
* @param srvName
|
|
39
|
+
* @param ordId
|
|
40
|
+
*/
|
|
41
|
+
exports.prepareCsn = function (csn, srvName, ordId) {
|
|
42
|
+
|
|
43
|
+
//
|
|
44
|
+
// ---------- step 0: check assumptions ---------------------------------------------------------
|
|
45
|
+
//
|
|
46
|
+
|
|
47
|
+
// TODO: check whether all assumptions are met, raise error if not
|
|
48
|
+
|
|
49
|
+
//
|
|
50
|
+
// ---------- step 1: resolve simple types ------------------------------------------------------
|
|
51
|
+
//
|
|
52
|
+
// Loop over all entity elements. If type is reference to simple type, replace it by
|
|
53
|
+
// corresponding built-in type and copy over the type's properties (unless already present)
|
|
54
|
+
|
|
55
|
+
for (let n in csn.definitions) {
|
|
56
|
+
let entity = csn.definitions[n]
|
|
57
|
+
if (entity.kind === 'entity' && entity.elements) {
|
|
58
|
+
|
|
59
|
+
for (let en in entity.elements) {
|
|
60
|
+
let element = entity.elements[en]
|
|
61
|
+
|
|
62
|
+
if (element.type && !element.type.startsWith('cds.')) {
|
|
63
|
+
let type = csn.definitions[element.type]
|
|
64
|
+
// TODO: warn/err if type not found
|
|
65
|
+
if (type?.kind === 'type') {
|
|
66
|
+
element.type = type.type
|
|
67
|
+
// copy props of type if not yet present
|
|
68
|
+
for (let prop in type) {
|
|
69
|
+
if (prop !== 'type' && prop !== 'kind' && !element[prop]) {
|
|
70
|
+
element[prop] = type[prop]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
//
|
|
80
|
+
// ---------- step 2: remove simple types ---------------------------------------------------------
|
|
81
|
+
//
|
|
82
|
+
// Remove all type definitions.
|
|
83
|
+
|
|
84
|
+
for (let n in csn.definitions) {
|
|
85
|
+
let typedef = csn.definitions[n]
|
|
86
|
+
if (typedef.kind === 'type') {
|
|
87
|
+
delete csn.definitions[n]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
//
|
|
92
|
+
// ---------- step 3: remove or rename annotations ------------------------------------------------
|
|
93
|
+
//
|
|
94
|
+
|
|
95
|
+
const annosToRemove = [
|
|
96
|
+
'__abapOriginalName',
|
|
97
|
+
'@AccessControl.authorizationCheck',
|
|
98
|
+
'@AccessControl.personalData',
|
|
99
|
+
'@Analytics.dataCategory',
|
|
100
|
+
'@Analytics.dataExtraction',
|
|
101
|
+
'@Metadata.allowExtensions',
|
|
102
|
+
'@ObjectModel.sapObjectNodeType.name',
|
|
103
|
+
'@ObjectModel.usageType',
|
|
104
|
+
'@VDM.viewType',
|
|
105
|
+
'@AbapCatalog',
|
|
106
|
+
'@ObjectModel.upperCase'
|
|
107
|
+
]
|
|
108
|
+
const annotationsToRename = {
|
|
109
|
+
'@EndUserText.label': '@title'
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
*
|
|
113
|
+
* @param obj
|
|
114
|
+
*/
|
|
115
|
+
function removeOrRenameAnnotations(obj) {
|
|
116
|
+
for (let prop in obj) {
|
|
117
|
+
// if prop has any entry in annosToRemove as prefix, delete it
|
|
118
|
+
for (let anno of annosToRemove) {
|
|
119
|
+
if (prop.startsWith(anno)) {
|
|
120
|
+
delete obj[prop]
|
|
121
|
+
break
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// if prop equals any entry in annosToRename, rename it
|
|
125
|
+
if (annotationsToRename[prop]) {
|
|
126
|
+
obj[annotationsToRename[prop]] = obj[prop]
|
|
127
|
+
delete obj[prop]
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (let n in csn.definitions) {
|
|
133
|
+
let entity = csn.definitions[n]
|
|
134
|
+
if (entity.kind === 'entity') {
|
|
135
|
+
removeOrRenameAnnotations(entity)
|
|
136
|
+
for (let en in entity.elements || {} ) {
|
|
137
|
+
removeOrRenameAnnotations(entity.elements[en])
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
//
|
|
143
|
+
// ---------- step 4: remove unused texts ---------------------------------------------------------
|
|
144
|
+
//
|
|
145
|
+
// Remove all texts that are no longer used.
|
|
146
|
+
// Usage example: "@EndUserText.label": "{i18n>I_CUSTOMERCOMPANY@ENDUSERTEXT.LABEL}"
|
|
147
|
+
|
|
148
|
+
// first loop over entities and their elements and collect all i18n keys used in annotation values ...
|
|
149
|
+
let i18nKeys = new Set()
|
|
150
|
+
/**
|
|
151
|
+
*
|
|
152
|
+
* @param obj
|
|
153
|
+
*/
|
|
154
|
+
function CollectKeys(obj) {
|
|
155
|
+
for (let prop in obj) {
|
|
156
|
+
if (prop.startsWith('@') && typeof obj[prop] === 'string' && obj[prop].startsWith('{i18n>')) {
|
|
157
|
+
let val = obj[prop]
|
|
158
|
+
i18nKeys.add(val.substring(6, val.length - 1)) // extract text key
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
for (let n in csn.definitions) {
|
|
163
|
+
let entity = csn.definitions[n]
|
|
164
|
+
if (entity.kind === 'entity') {
|
|
165
|
+
CollectKeys(entity)
|
|
166
|
+
for (let en in entity.elements || {} ) {
|
|
167
|
+
CollectKeys(entity.elements[en])
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// ... then remove all texts that are not used
|
|
172
|
+
for (let lang in csn.i18n) {
|
|
173
|
+
for (let key in csn.i18n[lang]) {
|
|
174
|
+
if (!i18nKeys.has(key)) {
|
|
175
|
+
delete csn.i18n[lang][key]
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
//
|
|
181
|
+
// ---------- step 5: wrap everyting into service if not yet done -------------------------------
|
|
182
|
+
//
|
|
183
|
+
// Prefix all entity names with service name. Adapt association targets.
|
|
184
|
+
// Add CAP specific annotations for DP handling.
|
|
185
|
+
|
|
186
|
+
if (srvName) {
|
|
187
|
+
if (!csn.definitions[srvName] || csn.definitions[srvName].kind !== 'service') {
|
|
188
|
+
// add the service
|
|
189
|
+
csn.definitions[srvName] = {kind: 'service'}
|
|
190
|
+
// loop over all entities and prefix names with service name
|
|
191
|
+
for (let n in csn.definitions) {
|
|
192
|
+
let entity = csn.definitions[n]
|
|
193
|
+
if (entity.kind === 'entity') {
|
|
194
|
+
// find association elements and rename the targets
|
|
195
|
+
for (let en in entity.elements || {} ) {
|
|
196
|
+
let element = entity.elements[en]
|
|
197
|
+
if (element?.type == 'cds.Association' || element?.type == 'cds.Composition') {
|
|
198
|
+
element.target = srvName + '.' + element.target
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
csn.definitions[srvName + '.' + n] = entity
|
|
202
|
+
delete csn.definitions[n]
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// add service annotations
|
|
207
|
+
Object.assign(csn.definitions[srvName], {
|
|
208
|
+
'@cds.dp.ordId': ordId,
|
|
209
|
+
'@cds.external': true,
|
|
210
|
+
'@data.product': true,
|
|
211
|
+
'@protocol' : 'none' // don't serve via OData
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
//
|
|
216
|
+
// ---------- step 6: move entity/element annotations into extensions section -------------------
|
|
217
|
+
//
|
|
218
|
+
|
|
219
|
+
for (let n in csn.definitions) {
|
|
220
|
+
let entity = csn.definitions[n]
|
|
221
|
+
if (entity.kind === 'entity') {
|
|
222
|
+
let annosEntity = { annotate: n }
|
|
223
|
+
for (let prop in entity) {
|
|
224
|
+
if (prop.startsWith('@')) {
|
|
225
|
+
annosEntity[prop] = entity[prop]
|
|
226
|
+
delete entity[prop]
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (let en in entity.elements || {} ) {
|
|
230
|
+
let element = entity.elements[en]
|
|
231
|
+
let annosElement = {}
|
|
232
|
+
for (let prop in element) {
|
|
233
|
+
if (prop.startsWith('@')) {
|
|
234
|
+
annosElement[prop] = element[prop]
|
|
235
|
+
delete element[prop]
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (Object.keys(annosElement).length > 0) {
|
|
239
|
+
annosEntity.elements ||= {}
|
|
240
|
+
annosEntity.elements[en] = annosElement
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (Object.keys(annosEntity).length > 1) {
|
|
244
|
+
csn.extensions ||= []
|
|
245
|
+
csn.extensions.push(annosEntity)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
//
|
|
251
|
+
// ---------- step 7: add Java specific annotations --------------------------------------------
|
|
252
|
+
//
|
|
253
|
+
// In S/4 DPs, names of association elements oftentimes begin with `_`, and the respective FK
|
|
254
|
+
// has the same name w/o `_`, like:
|
|
255
|
+
// * Customer : ... // the FK
|
|
256
|
+
// * _Customer : Association ...
|
|
257
|
+
// When generating accessor classes, CAP Java ignores the `_` prefix, which leads to duplicate names.
|
|
258
|
+
//
|
|
259
|
+
// If an association-like element's name starts with `_` and there is an element with the same name w/o '_',
|
|
260
|
+
// we add an annotation to change the Java name.
|
|
261
|
+
// This is done after the separation of all other entities into a separate file, so that these
|
|
262
|
+
// annotations are visible directly in the entity definition
|
|
263
|
+
for (let n in csn.definitions) {
|
|
264
|
+
let entity = csn.definitions[n]
|
|
265
|
+
if (entity.kind === 'entity') {
|
|
266
|
+
// find offensive names
|
|
267
|
+
for (let en in entity.elements || {} ) {
|
|
268
|
+
let element = entity.elements[en]
|
|
269
|
+
if (en.startsWith('_') && entity.elements[en.slice(1)] &&
|
|
270
|
+
element?.type == 'cds.Association' || element?.type == 'cds.Composition') {
|
|
271
|
+
element['@cds.java.name'] = 'to' + en.slice(1)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
//
|
|
278
|
+
// ----------------------------------------------------------------------------------------------
|
|
279
|
+
//
|
|
280
|
+
|
|
281
|
+
return csn
|
|
282
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// input: single prepared CSN for a Data Product
|
|
2
|
+
// output: array with file names and contents, like
|
|
3
|
+
// [ {path: 'Customer.cds', src: '...content...'},
|
|
4
|
+
// {path: 'annotations.cds', src: '...content...'},
|
|
5
|
+
// {path: 'index.cds', src: '...content...'},
|
|
6
|
+
// {path: '_i18n/i18n_de.properties', src: '...content...'},
|
|
7
|
+
// ...
|
|
8
|
+
// ]
|
|
9
|
+
|
|
10
|
+
'use strict'
|
|
11
|
+
|
|
12
|
+
const path = require('node:path')
|
|
13
|
+
const { EOL } = require('node:os')
|
|
14
|
+
const { compiler: compile } = require ('../../lib/cds')
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
* @param csn
|
|
19
|
+
* @param srvName_short
|
|
20
|
+
*/
|
|
21
|
+
function prepareFiles(csn, srvName_short) {
|
|
22
|
+
const {definitions, extensions, i18n} = csn
|
|
23
|
+
|
|
24
|
+
let files = []
|
|
25
|
+
|
|
26
|
+
// main cds file
|
|
27
|
+
const cdl_main = compile.to.cdl({definitions: definitions})
|
|
28
|
+
files.push({
|
|
29
|
+
path: srvName_short + '.cds',
|
|
30
|
+
src: cdl_main.model
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// annotations file
|
|
34
|
+
if (extensions) {
|
|
35
|
+
const using = `using from './${srvName_short}';` + EOL + EOL
|
|
36
|
+
const cdl_annos = compile.to.cdl({extensions: extensions})
|
|
37
|
+
files.push({
|
|
38
|
+
path: 'annotations.cds',
|
|
39
|
+
src: using + cdl_annos.model
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// index file
|
|
44
|
+
const index_text = `using from './${srvName_short}';` + EOL
|
|
45
|
+
+ (extensions ? ('using from \'./annotations\';' + EOL) : '')
|
|
46
|
+
files.push({
|
|
47
|
+
path: 'index.cds',
|
|
48
|
+
src: index_text
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// text files
|
|
52
|
+
for (const langu in i18n) {
|
|
53
|
+
const i18n_block = i18n[langu]
|
|
54
|
+
let out = ''
|
|
55
|
+
for (const key in i18n_block) {
|
|
56
|
+
let txt = i18n_block[key]
|
|
57
|
+
txt = txt.replace(/'/g, "\\'")
|
|
58
|
+
out += key + '=' + txt + EOL
|
|
59
|
+
}
|
|
60
|
+
files.push({
|
|
61
|
+
path: path.join('_i18n', 'i18n_' + langu + '.properties'),
|
|
62
|
+
src: out
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return files
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = prepareFiles
|
package/bin/export.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const cds = require ('../lib/cds.js')
|
|
3
|
+
const { regex4 } = require ('./util/cli.js')
|
|
4
|
+
const { Minify } = require ('./minify.js')
|
|
5
|
+
|
|
6
|
+
class Export extends Minify {
|
|
7
|
+
|
|
8
|
+
options = Export.options // for IntelliSense
|
|
9
|
+
static options = { ...super.options,
|
|
10
|
+
services: /all/,
|
|
11
|
+
from: ['*'],
|
|
12
|
+
to: './apis/*',
|
|
13
|
+
cleanse: { _excluding: 'keep',
|
|
14
|
+
'@restrict': true,
|
|
15
|
+
'@requires': true,
|
|
16
|
+
'@cds.api.': true,
|
|
17
|
+
'@UI.': true,
|
|
18
|
+
includes: true,
|
|
19
|
+
ignored: true,
|
|
20
|
+
assocs: true,
|
|
21
|
+
texts: true,
|
|
22
|
+
views: true,
|
|
23
|
+
},
|
|
24
|
+
reuse: [ '@sap/cds/common' ],
|
|
25
|
+
force: false,
|
|
26
|
+
texts: true,
|
|
27
|
+
data: true,
|
|
28
|
+
as: 'csn',
|
|
29
|
+
for: 'npm',
|
|
30
|
+
}
|
|
31
|
+
static shortcuts = { ...super.shortcuts,
|
|
32
|
+
to:'2',
|
|
33
|
+
for:'4'
|
|
34
|
+
}
|
|
35
|
+
static filename = __filename
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
parseArgs (conf) {
|
|
39
|
+
if (!process.stdout.isTTY) this.options.to = '-'
|
|
40
|
+
let [sources,o] = super.parseArgs (conf)
|
|
41
|
+
// -----------------------------------------------------------------------
|
|
42
|
+
// Java-specific adjustments -> shall this stay for GA?
|
|
43
|
+
if (o.for === 'mvn' && !o.to.startsWith('./') && !o.to.startsWith('/'))
|
|
44
|
+
o.to = './src/main/resources/cds/' + o.to
|
|
45
|
+
// -----------------------------------------------------------------------
|
|
46
|
+
if (o.from && o.from != '*') { // variant: cds-export <services> --from <models>
|
|
47
|
+
if (o.services == '/all/') o.services = regex4 (sources)
|
|
48
|
+
sources = o.from
|
|
49
|
+
}
|
|
50
|
+
return [ this.sources = o.from = sources, this.options = o ]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async requires (models) {
|
|
55
|
+
if (models == 'none' || models == '') return {check(){}}
|
|
56
|
+
const { definitions: defs, $sources } = await cds.load (models, 'parsed')
|
|
57
|
+
const $map = Object.fromEntries ($sources.map ((s,i) => [ local(s), models[i] ]))
|
|
58
|
+
return Object.assign (new Set, { check(n) {
|
|
59
|
+
return n in defs && this.add ($map[defs[n].$location.file])
|
|
60
|
+
}})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async cleanse (csn, o = this.options) {
|
|
65
|
+
|
|
66
|
+
const reused = await this.requires (o.reuse)
|
|
67
|
+
const { definitions: defs } = csn = { ...csn, definitions: { ...csn.definitions }}
|
|
68
|
+
const { cleanse } = o
|
|
69
|
+
|
|
70
|
+
// cleanse model...
|
|
71
|
+
for (const [each,d] of Object.entries (defs)) {
|
|
72
|
+
if (reused.check(each)) delete defs [each]
|
|
73
|
+
else if (cleanse.ignored && d['@cds.api.ignore']) delete defs [each]
|
|
74
|
+
else if (cleanse.texts && each.endsWith('.texts')) delete defs [each]
|
|
75
|
+
else if (d.kind === 'entity') _cleanse (d, cleanse)
|
|
76
|
+
}
|
|
77
|
+
if (reused.size > 0) csn.requires = [...reused]
|
|
78
|
+
|
|
79
|
+
// cleanse entities...
|
|
80
|
+
function _cleanse (d, cleanse) {
|
|
81
|
+
if (cleanse.managed) {
|
|
82
|
+
delete d.elements.createdAt
|
|
83
|
+
delete d.elements.createdBy
|
|
84
|
+
delete d.elements.modifiedAt
|
|
85
|
+
delete d.elements.modifiedBy
|
|
86
|
+
}
|
|
87
|
+
if (cleanse.users) {
|
|
88
|
+
delete d.elements.createdBy
|
|
89
|
+
delete d.elements.modifiedBy
|
|
90
|
+
}
|
|
91
|
+
if (cleanse.views) {
|
|
92
|
+
delete d.projection
|
|
93
|
+
delete d.query
|
|
94
|
+
}
|
|
95
|
+
if (cleanse.texts) {
|
|
96
|
+
delete d.elements.localized
|
|
97
|
+
delete d.elements.texts
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// cleanse elements...
|
|
101
|
+
for (const [each,e] of Object.entries(d.elements)) {
|
|
102
|
+
if (cleanse.ignored && e['@cds.api.ignore']) return delete d.elements[each]
|
|
103
|
+
if (cleanse.texts) delete e.localized
|
|
104
|
+
if (cleanse.assocs) delete e.keys // cleanse .keys added by cdsc
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return super.cleanse (csn,o)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async export (minified,o) {
|
|
112
|
+
if (this.options.to === '-') return super.print (minified)
|
|
113
|
+
if (this.options.to.endsWith('/*')) this.options.to = this.export_to()
|
|
114
|
+
console.log(`\n${this.name}ing APIs to`, this.options.to, '...\n')
|
|
115
|
+
const exports = []; exports.push(...this.export_models (minified))
|
|
116
|
+
if (o.texts) exports.push (...this.export_texts (minified))
|
|
117
|
+
if (o.data) exports.push (this.export_data (minified))
|
|
118
|
+
if (o.for === 'npm') exports.push (this.export_package_json())
|
|
119
|
+
await Promise.all (exports)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export_to() {
|
|
123
|
+
const to = this.options.to.replace('/*','')
|
|
124
|
+
const { resolve, basename } = cds.utils.path
|
|
125
|
+
const { sources } = this
|
|
126
|
+
if (sources.length === 1 && sources[0].endsWith('.cds'))
|
|
127
|
+
return resolve (to, this.package_name = basename(sources[0]).slice(0,-4))
|
|
128
|
+
const defs = this.min.definitions
|
|
129
|
+
const services = Object.keys(defs).filter (k => defs[k].kind === 'service')
|
|
130
|
+
if (services.length === 1)
|
|
131
|
+
return resolve (to, this.package_name = services[0])
|
|
132
|
+
else throw `\n Can't determine an output folder. \n Please specify one using --to option. \n`
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async export_package_json (pkg, o = this.options) {
|
|
136
|
+
await this.write ('// just a tag file for plug & play') .to ('cds-plugin.js', false)
|
|
137
|
+
if (!o.force && exists (path.join (this.options.to, 'package.json'))) return null
|
|
138
|
+
if (!pkg) {
|
|
139
|
+
let { name, version } = await read ('package.json')
|
|
140
|
+
let requires = Object.fromEntries (Object.entries(this.min.definitions)
|
|
141
|
+
.filter (([,d]) => d.kind === 'service')
|
|
142
|
+
.map (([name]) => [ name, true ]))
|
|
143
|
+
name += '-' + cds.utils.path.basename (this.options.to)
|
|
144
|
+
pkg = { name, version, cds:{requires} }
|
|
145
|
+
}
|
|
146
|
+
return this.write (pkg) .to ('package.json')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
*export_models (model, o = this.options) {
|
|
150
|
+
for (let d of Object.values(model.definitions))
|
|
151
|
+
if (d.kind === 'service') d['@cds.external'] ??= 2
|
|
152
|
+
yield this.write (
|
|
153
|
+
`// This file acts as a central facade to exported service definitions.\n`+
|
|
154
|
+
`// You can modify it to tweak things, without your changes being overridden.\n`+
|
|
155
|
+
`using from './services';`
|
|
156
|
+
) .to ('index.cds', false)
|
|
157
|
+
if (o.as === 'cdl') {
|
|
158
|
+
const cdl = cds.compile.to.cdl (model)
|
|
159
|
+
yield this.write (cdl) .to ('services.cds')
|
|
160
|
+
} else {
|
|
161
|
+
const csn = cds.compile.to.json (model)
|
|
162
|
+
yield this.write (csn) .to ('services.csn')
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
*export_texts() {
|
|
167
|
+
|
|
168
|
+
const common = cds.load.properties (require.resolve('@sap/cds/_i18n/i18n.properties'))
|
|
169
|
+
const bundles = cds.i18n.labels.all()
|
|
170
|
+
const defaults = cds.i18n.labels.defaults
|
|
171
|
+
const serialize = texts => Object.entries(texts).map(([k,v]) => k +'='+ v.replace(/'/g, "''")).join('\n')
|
|
172
|
+
const write = texts => ({ to: file => this.write(serialize(texts)).to(file) })
|
|
173
|
+
|
|
174
|
+
// minify fallback bundle entries
|
|
175
|
+
const fallback = bundles[''] = {}
|
|
176
|
+
for (let key in defaults) {
|
|
177
|
+
if (key in common) continue
|
|
178
|
+
fallback[key] = defaults[key]
|
|
179
|
+
}
|
|
180
|
+
yield write(fallback) .to ('_i18n/i18n.properties')
|
|
181
|
+
|
|
182
|
+
// minify other bundles
|
|
183
|
+
for (let [locale,texts] of Object.entries(bundles)) {
|
|
184
|
+
const min = bundles[locale] = {}
|
|
185
|
+
for (let key in texts) {
|
|
186
|
+
if (key in common) continue
|
|
187
|
+
if (texts[key] === defaults[key]) continue
|
|
188
|
+
min[key] = texts[key]
|
|
189
|
+
}
|
|
190
|
+
if (Object.keys(min).length > 0) write(min) .to (`_i18n/i18n_${locale}.properties`)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async export_data (model) {
|
|
195
|
+
const db = cds.db = await cds.deploy ('*',{silent:true})
|
|
196
|
+
const out = path.resolve (this.options.to, 'data')
|
|
197
|
+
await fs.promises.rm (out, { recursive: true, force: true })
|
|
198
|
+
await mkdirp (this.options.to, 'data')
|
|
199
|
+
for (let each of cds.linked(model).entities) {
|
|
200
|
+
await _export (db.model.definitions[each.name])
|
|
201
|
+
}
|
|
202
|
+
async function _export (e) {
|
|
203
|
+
if (!e || e['@cds.persistence.skip']) return
|
|
204
|
+
const file = path.join (out, `${e.name}.csv`)
|
|
205
|
+
const columns = Object.keys(e.elements).filter(k => !e.elements[k]['@Core.Computed'])
|
|
206
|
+
const rows = await SELECT.from(e).columns(columns)
|
|
207
|
+
let csv = fs.createWriteStream (file), i=0
|
|
208
|
+
for (let r of await rows) {
|
|
209
|
+
if (i++ === 0) csv.write (Object.keys(r).join(',') +'\n')
|
|
210
|
+
csv.write (Object.values(r).map(quoted).join(',') +'\n')
|
|
211
|
+
}
|
|
212
|
+
csv.end()
|
|
213
|
+
console.log (DIMMED,' >', local(file), RESET)
|
|
214
|
+
}
|
|
215
|
+
function quoted (x) {
|
|
216
|
+
if (typeof x === 'string') {
|
|
217
|
+
if (x.startsWith('"') && x.endsWith('"')) return x // already quoted
|
|
218
|
+
if (x.endsWith(',')) x = x.slice(0, -1) // remove trailing comma
|
|
219
|
+
if (x.includes(',') || x.includes('\n')) return `"${x.replace(/"/g, '""')}"`
|
|
220
|
+
}
|
|
221
|
+
return x
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
write (x) { return { to: async (filename, force=true) => {
|
|
226
|
+
if (!x) return; else if (x.then) x = await x
|
|
227
|
+
const file = path.join (this.options.to, filename)
|
|
228
|
+
if (!force && !this.options.force && exists(file)) return
|
|
229
|
+
console.log (DIMMED,' >', local(file), RESET)
|
|
230
|
+
return write(x).to (file)
|
|
231
|
+
}}}
|
|
232
|
+
|
|
233
|
+
print = undefined // tells the base class to use this.export()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
const { read, write, exists, mkdirp, local, path, fs } = cds.utils
|
|
238
|
+
const { DIMMED, RESET } = cds.utils.colors
|
|
239
|
+
|
|
240
|
+
module.exports = Object.assign (Export._for_cds_dk(), { Export })
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
|
|
2
|
+
# SYNOPSIS
|
|
3
|
+
|
|
4
|
+
*cds export* [ --services ] <patterns> --from <sources> [ ...options ]
|
|
5
|
+
*cds export* [ <sources> ] [ ...options ]
|
|
6
|
+
|
|
7
|
+
# OPTIONS
|
|
8
|
+
|
|
9
|
+
*-s | --services* <patterns>
|
|
10
|
+
*-f | --from* <sources>
|
|
11
|
+
*-2 | --to* <folder>
|
|
12
|
+
*-4 | --for* <npm|mvn>
|
|
13
|
+
*-a | --as* <csn|cdl>
|
|
14
|
+
|
|
15
|
+
*-c | --cleanse* <properties>
|
|
16
|
+
*-k | --keep* <kinds>
|
|
17
|
+
*-x | --skip* <kinds>
|
|
18
|
+
*-d | --data*
|
|
19
|
+
*-t | --texts*
|
|
20
|
+
|
|
21
|
+
*-y | --dry*
|
|
22
|
+
*-i | --inspect*
|
|
23
|
+
*-f | --force*
|
|
24
|
+
*-h | --help*
|
|
25
|
+
|
|
26
|
+
Run with *--debug* to list all options with their defaults.
|
|
27
|
+
|
|
28
|
+
# EXAMPLES
|
|
29
|
+
|
|
30
|
+
*cds export* ?
|
|
31
|
+
*cds export* --help
|
|
32
|
+
*cds export* srv/cat-service.cds --dry
|
|
33
|
+
*cds export* -s cat*s --dry
|
|
34
|
+
*cds export* -s cat*s --cleanse @,all | cds -2 cdl
|
|
35
|
+
*cds export* -s cat*s --inspect=2
|
|
36
|
+
*cds export* -s cat*s --as cdl
|
|
37
|
+
*cds export* -s cat*s
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
# SYNOPSIS
|
|
3
|
+
|
|
4
|
+
*cds import* [ --services ] <patterns> --from <sources> [ ...options ]
|
|
5
|
+
*cds import* [ <sources> ] [ ...options ]
|
|
6
|
+
|
|
7
|
+
# ANALYSIS
|
|
8
|
+
|
|
9
|
+
The *cds import* command is used to import definitions from other sources
|
|
10
|
+
into the current project. It is the exact inverse to *cds export*, and only
|
|
11
|
+
used when the sources didn't provide a client package with *cds export*.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
# SYNOPSIS
|
|
3
|
+
|
|
4
|
+
*cds minify* <models> [ ...options ]
|
|
5
|
+
|
|
6
|
+
# OPTIONS
|
|
7
|
+
|
|
8
|
+
*-c | --cleanse* <properties>
|
|
9
|
+
*-k | --keep* <kinds>
|
|
10
|
+
*-x | --skip* <kinds>
|
|
11
|
+
*-2 | --to* <folder>
|
|
12
|
+
*-i | --inspect*
|
|
13
|
+
*-y | --dry*
|
|
14
|
+
*-h | --help*
|
|
15
|
+
|
|
16
|
+
Run with *--debug* to list all options with their defaults.
|
|
17
|
+
|
|
18
|
+
# EXAMPLES
|
|
19
|
+
|
|
20
|
+
*cds minify* ?
|
|
21
|
+
*cds minify* -h
|
|
22
|
+
*cds minify* --debug
|
|
23
|
+
*cds minify* srv/cat-service.cds -y
|