@sap/cds-dk 9.3.2 → 9.4.2

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.
Files changed (113) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/bin/bind.js +1 -1
  3. package/bin/cds.js +3 -0
  4. package/bin/compile/index.js +53 -3
  5. package/bin/data-products/get-csn-via-dp-cli.js +41 -0
  6. package/bin/data-products/get-csn.js +76 -0
  7. package/bin/data-products/import.js +114 -0
  8. package/bin/data-products/index.js +30 -0
  9. package/bin/data-products/merge-csns.js +46 -0
  10. package/bin/data-products/prepare-csn.js +282 -0
  11. package/bin/data-products/prepare-files.js +69 -0
  12. package/bin/export.js +240 -0
  13. package/bin/help/export.md +37 -0
  14. package/bin/help/import.md +11 -0
  15. package/bin/help/minify.md +23 -0
  16. package/bin/import.js +13 -3
  17. package/bin/inspect.js +226 -0
  18. package/bin/minify.js +123 -0
  19. package/bin/up.js +2 -2
  20. package/bin/util/cli.js +132 -0
  21. package/bin/util/log.js +2 -0
  22. package/bin/version.js +2 -1
  23. package/bin/watch.js +21 -3
  24. package/lib/bind/shared.js +3 -1
  25. package/lib/build/buildTaskEngine.js +2 -1
  26. package/lib/build/plugins/helm/files/templates/NOTES.txt +1 -1
  27. package/lib/build/plugins/helm/index.js +1 -2
  28. package/lib/build/provider/internalBuildTaskProvider.js +3 -3
  29. package/lib/build/provider/nodejs/index.js +62 -3
  30. package/lib/deploy/index.js +1 -1
  31. package/lib/deploy/to-hana/hana.js +18 -16
  32. package/lib/import/odata/v2parser.js +2 -2
  33. package/lib/import/odata/v4parser.js +21 -2
  34. package/lib/import/odata.package.json +4 -4
  35. package/lib/init/constants.js +4 -1
  36. package/lib/init/index.js +6 -5
  37. package/lib/init/mvn.js +3 -2
  38. package/lib/init/projectReader.js +65 -55
  39. package/lib/init/registries/mta.js +6 -0
  40. package/lib/init/template/ams/files/manifest.yml.hbs +1 -1
  41. package/lib/init/template/ams/files/mta.yaml.hbs +1 -1
  42. package/lib/init/template/ams/index.js +7 -6
  43. package/lib/init/template/app-frontend/files/app-package.json.hbs +20 -0
  44. package/lib/init/template/app-frontend/files/index.html.hbs +34 -0
  45. package/lib/init/template/app-frontend/files/manifest.json +13 -0
  46. package/lib/init/template/app-frontend/files/mta.yaml.hbs +64 -0
  47. package/lib/init/template/app-frontend/files/package.json.hbs +17 -0
  48. package/lib/init/template/app-frontend/files/ui5-deploy.yaml.hbs +25 -0
  49. package/lib/init/template/app-frontend/files/ui5.yaml.hbs +6 -0
  50. package/lib/init/template/app-frontend/files/values.yaml.hbs +20 -0
  51. package/lib/init/template/app-frontend/files/xs-app.json.hbs +17 -0
  52. package/lib/init/template/app-frontend/index.js +98 -0
  53. package/lib/init/template/application-logging/index.js +13 -10
  54. package/lib/init/template/approuter/files/mta.yaml.hbs +0 -2
  55. package/lib/init/template/approuter/index.js +15 -9
  56. package/lib/init/template/attachments/index.js +7 -13
  57. package/lib/init/template/audit-logging/files/package.json.hbs +5 -0
  58. package/lib/init/template/audit-logging/index.js +4 -4
  59. package/lib/init/template/cloud-logging/index.js +13 -10
  60. package/lib/init/template/connectivity/index.js +3 -3
  61. package/lib/init/template/destination/index.js +6 -6
  62. package/lib/init/template/dynatrace/index.js +13 -10
  63. package/lib/init/template/enterprise-messaging/index.js +5 -5
  64. package/lib/init/template/enterprise-messaging-shared/index.js +4 -4
  65. package/lib/init/template/extensibility/index.js +2 -2
  66. package/lib/init/template/file-based-messaging/index.js +2 -2
  67. package/lib/init/template/github-actions/index.js +3 -3
  68. package/lib/init/template/hana/index.js +19 -22
  69. package/lib/init/template/handler/index.js +1 -0
  70. package/lib/init/template/helm/index.js +34 -21
  71. package/lib/init/template/html5-repo/files/containerize.yaml.hbs +0 -1
  72. package/lib/init/template/html5-repo/files/mta.yaml.hbs +10 -2
  73. package/lib/init/template/html5-repo/index.js +8 -6
  74. package/lib/init/template/http/index.js +31 -10
  75. package/lib/init/template/ias/files/manifest.yml.hbs +1 -1
  76. package/lib/init/template/ias/files/mta.yaml.hbs +9 -5
  77. package/lib/init/template/ias/files/values.yaml.hbs +14 -2
  78. package/lib/init/template/ias/index.js +8 -8
  79. package/lib/init/template/kafka/index.js +3 -3
  80. package/lib/init/template/local-messaging/index.js +2 -2
  81. package/lib/init/template/malware-scanner/index.js +3 -7
  82. package/lib/init/template/mta/files/mta.yaml.hbs +10 -0
  83. package/lib/init/template/multitenancy/files/mta.yaml.hbs +16 -3
  84. package/lib/init/template/multitenancy/files/package.json.hbs +5 -0
  85. package/lib/init/template/multitenancy/files/values.yaml.hbs +1 -6
  86. package/lib/init/template/multitenancy/index.js +8 -10
  87. package/lib/init/template/notifications/index.js +2 -2
  88. package/lib/init/template/portal/files/mta.yaml.hbs +18 -2
  89. package/lib/init/template/portal/index.js +7 -7
  90. package/lib/init/template/postgres/index.js +4 -4
  91. package/lib/init/template/redis/index.js +2 -2
  92. package/lib/init/template/redis-messaging/index.js +2 -2
  93. package/lib/init/template/sample/files/app/admin-books/fiori-service.cds +2 -2
  94. package/lib/init/template/sample/files/db/schema.cds +5 -8
  95. package/lib/init/template/sample/files/javascript/admin-service.js.hbs +24 -0
  96. package/lib/init/template/sample/files/javascript/cat-service.js.hbs +43 -0
  97. package/lib/init/template/sample/files/typescript/admin-service.ts.hbs +16 -0
  98. package/lib/init/template/sample/files/typescript/cat-service.ts.hbs +31 -0
  99. package/lib/init/template/test/files/test/test.xs.hbs +1 -1
  100. package/lib/init/template/test/index.js +2 -2
  101. package/lib/init/template/tiny-sample/index.js +1 -1
  102. package/lib/init/template/typer/files/xsconfig.json +2 -1
  103. package/lib/init/template/typescript/files/package.json +1 -1
  104. package/lib/init/template/typescript/files/tsconfig.json +4 -1
  105. package/lib/init/template/workzone-standard/files/mta.yaml.hbs +4 -0
  106. package/lib/init/template/workzone-standard/index.js +8 -8
  107. package/lib/init/template/xsuaa/files/mta.yaml.hbs +0 -2
  108. package/lib/init/template/xsuaa/files/package.json.hbs +3 -0
  109. package/lib/init/template/xsuaa/index.js +23 -18
  110. package/lib/lint/eslintHelp.json +1 -1
  111. package/lib/util/term.js +2 -4
  112. package/npm-shrinkwrap.json +28 -28
  113. 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