@sap/cds 6.1.3 → 6.2.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.
Files changed (206) hide show
  1. package/CHANGELOG.md +77 -8
  2. package/apis/cds.d.ts +18 -6
  3. package/apis/connect.d.ts +1 -1
  4. package/apis/cqn.d.ts +1 -1
  5. package/apis/log.d.ts +23 -5
  6. package/apis/ql.d.ts +128 -61
  7. package/apis/services.d.ts +11 -0
  8. package/apis/test.d.ts +61 -0
  9. package/apis/utils.d.ts +15 -0
  10. package/app/fiori/preview.js +1 -0
  11. package/bin/build/buildTaskEngine.js +70 -22
  12. package/bin/build/buildTaskFactory.js +18 -11
  13. package/bin/build/buildTaskHandler.js +1 -1
  14. package/bin/build/buildTaskProviderFactory.js +3 -13
  15. package/bin/build/constants.js +0 -1
  16. package/bin/build/index.js +14 -6
  17. package/bin/build/provider/buildTaskHandlerEdmx.js +2 -3
  18. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +2 -2
  19. package/bin/build/provider/buildTaskHandlerInternal.js +3 -6
  20. package/bin/build/provider/buildTaskProviderInternal.js +51 -39
  21. package/bin/build/provider/fiori/index.js +3 -3
  22. package/bin/build/provider/hana/2migration.js +1 -1
  23. package/bin/build/provider/hana/index.js +34 -27
  24. package/bin/build/provider/java/index.js +6 -7
  25. package/bin/build/provider/mtx/index.js +20 -18
  26. package/bin/build/provider/mtx/resourcesTarBuilder.js +8 -11
  27. package/bin/build/provider/mtx-sidecar/index.js +13 -17
  28. package/bin/build/provider/nodejs/index.js +8 -7
  29. package/bin/build/util.js +22 -4
  30. package/bin/cds.js +8 -4
  31. package/bin/deploy/to-hana/cfUtil.js +53 -18
  32. package/bin/mtx/in-cds.js +1 -0
  33. package/bin/serve.js +37 -30
  34. package/lib/auth/basic-auth.js +33 -0
  35. package/lib/auth/dummy-auth.js +7 -0
  36. package/lib/auth/ias-auth.js +2 -0
  37. package/lib/auth/index.js +31 -0
  38. package/lib/auth/jwt-auth.js +3 -0
  39. package/lib/auth/mocked-users.js +72 -0
  40. package/lib/auth/passport-basic.js +12 -0
  41. package/lib/auth/passport-digest.js +14 -0
  42. package/lib/auth/xsuaa-auth.js +3 -0
  43. package/lib/compile/cds-compile.js +3 -3
  44. package/lib/compile/to/cdl.js +5 -1
  45. package/lib/compile/to/edm.js +8 -0
  46. package/lib/compile/to/gql.js +1 -0
  47. package/lib/compile/to/json.js +30 -5
  48. package/lib/compile/to/sql.js +3 -1
  49. package/lib/core/index.js +5 -1
  50. package/lib/dbs/cds-deploy.js +36 -6
  51. package/lib/env/cds-env.js +15 -5
  52. package/lib/env/cds-requires.js +51 -58
  53. package/lib/env/defaults.js +1 -0
  54. package/lib/env/schemas/cds-package.json +4 -0
  55. package/lib/env/schemas/cds-rc.json +63 -77
  56. package/lib/i18n/localize.js +16 -5
  57. package/lib/index.js +9 -4
  58. package/lib/log/cds-error.js +4 -6
  59. package/lib/log/cds-log.js +89 -53
  60. package/lib/log/service/index.js +1 -0
  61. package/lib/ql/CREATE.js +2 -5
  62. package/lib/ql/DELETE.js +1 -1
  63. package/lib/ql/DROP.js +1 -3
  64. package/lib/ql/INSERT.js +3 -3
  65. package/lib/ql/Query.js +10 -23
  66. package/lib/ql/SELECT.js +1 -2
  67. package/lib/ql/UPDATE.js +2 -2
  68. package/lib/ql/Whereable.js +7 -15
  69. package/lib/ql/cds-ql.js +9 -3
  70. package/lib/req/cds-context.js +11 -3
  71. package/lib/req/context.js +29 -23
  72. package/lib/req/locale.js +9 -5
  73. package/lib/req/request.js +1 -0
  74. package/lib/req/user.js +2 -1
  75. package/lib/srv/cds-connect.js +1 -1
  76. package/lib/srv/cds-serve.js +21 -14
  77. package/lib/srv/middlewares/cds-context.js +29 -0
  78. package/lib/srv/middlewares/ctx-model.js +24 -0
  79. package/lib/srv/middlewares/errors.js +9 -0
  80. package/lib/srv/middlewares/index.js +22 -0
  81. package/lib/srv/middlewares/sap-statistics.js +13 -0
  82. package/lib/srv/middlewares/trace.js +102 -0
  83. package/lib/srv/protocols/_legacy.js +42 -0
  84. package/lib/srv/protocols/graphql.js +39 -0
  85. package/lib/srv/protocols/hcql.js +37 -0
  86. package/lib/srv/protocols/index.js +86 -0
  87. package/lib/srv/protocols/odata-v2-proxy.js +3767 -0
  88. package/lib/srv/protocols/odata-v2.js +26 -0
  89. package/lib/srv/protocols/odata-v4.js +16 -0
  90. package/lib/srv/protocols/rest.js +13 -0
  91. package/lib/srv/srv-api.js +5 -0
  92. package/lib/srv/srv-models.js +4 -6
  93. package/lib/utils/axios.js +3 -2
  94. package/lib/utils/cds-test.js +27 -21
  95. package/lib/utils/cds-utils.js +19 -20
  96. package/lib/utils/tar.js +175 -0
  97. package/libx/_runtime/audit/generic/personal/utils.js +18 -7
  98. package/libx/_runtime/audit/utils/v2.js +1 -0
  99. package/libx/_runtime/auth/index.js +4 -0
  100. package/libx/_runtime/auth/strategies/ias-auth.js +76 -0
  101. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +8 -3
  102. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +15 -4
  103. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
  104. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +1 -1
  105. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
  106. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +9 -0
  107. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriInfo.js +5 -1
  108. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +12 -0
  109. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js +6 -2
  110. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/RequestValidator.js +47 -7
  111. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  112. package/libx/_runtime/cds-services/util/assert.js +4 -0
  113. package/libx/_runtime/common/aspects/relation.js +1 -1
  114. package/libx/_runtime/common/composition/data.js +61 -15
  115. package/libx/_runtime/common/composition/delete.js +0 -1
  116. package/libx/_runtime/common/composition/insert.js +0 -1
  117. package/libx/_runtime/common/composition/tree.js +4 -10
  118. package/libx/_runtime/common/composition/update.js +44 -21
  119. package/libx/_runtime/common/generic/auth/capabilities.js +8 -10
  120. package/libx/_runtime/common/generic/crud.js +1 -2
  121. package/libx/_runtime/common/generic/etag.js +4 -4
  122. package/libx/_runtime/common/generic/input.js +4 -4
  123. package/libx/_runtime/common/generic/paging.js +3 -3
  124. package/libx/_runtime/common/generic/put.js +3 -3
  125. package/libx/_runtime/common/generic/sorting.js +4 -4
  126. package/libx/_runtime/common/generic/temporal.js +3 -3
  127. package/libx/_runtime/common/i18n/messages.properties +0 -7
  128. package/libx/_runtime/common/utils/cqn2cqn4sql.js +11 -6
  129. package/libx/_runtime/common/utils/csn.js +0 -28
  130. package/libx/_runtime/common/utils/draft.js +8 -1
  131. package/libx/_runtime/common/utils/path.js +7 -1
  132. package/libx/_runtime/common/utils/resolveView.js +2 -3
  133. package/libx/_runtime/db/data-conversion/post-processing.js +3 -44
  134. package/libx/_runtime/db/generic/input.js +3 -3
  135. package/libx/_runtime/db/sql-builder/dataTypes.js +4 -0
  136. package/libx/_runtime/fiori/generic/activate.js +2 -2
  137. package/libx/_runtime/fiori/generic/before.js +40 -72
  138. package/libx/_runtime/fiori/generic/cancel.js +2 -2
  139. package/libx/_runtime/fiori/generic/delete.js +2 -2
  140. package/libx/_runtime/fiori/generic/edit.js +2 -2
  141. package/libx/_runtime/fiori/generic/new.js +2 -2
  142. package/libx/_runtime/fiori/generic/patch.js +49 -37
  143. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  144. package/libx/_runtime/fiori/generic/read.js +27 -37
  145. package/libx/_runtime/fiori/utils/where.js +4 -2
  146. package/libx/_runtime/hana/Service.js +1 -3
  147. package/libx/_runtime/hana/conversion.js +3 -0
  148. package/libx/_runtime/hana/driver.js +33 -3
  149. package/libx/_runtime/hana/dynatrace.js +1 -0
  150. package/libx/_runtime/hana/search2Contains.js +12 -1
  151. package/libx/_runtime/hana/search2cqn4sql.js +10 -27
  152. package/libx/_runtime/hana/streaming.js +1 -0
  153. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
  154. package/libx/_runtime/messaging/common-utils/AMQPClient.js +1 -0
  155. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +5 -2
  156. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -0
  157. package/libx/_runtime/messaging/enterprise-messaging.js +62 -3
  158. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  159. package/libx/_runtime/messaging/redis-messaging.js +1 -0
  160. package/libx/_runtime/remote/Service.js +2 -2
  161. package/libx/_runtime/remote/utils/client.js +8 -3
  162. package/libx/_runtime/remote/utils/data.js +7 -2
  163. package/libx/_runtime/sqlite/Service.js +18 -7
  164. package/libx/_runtime/sqlite/conversion.js +3 -0
  165. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +3 -3
  166. package/libx/_runtime/sqlite/localized.js +8 -8
  167. package/libx/odata/afterburner.js +39 -7
  168. package/libx/odata/cqn2odata.js +6 -3
  169. package/libx/odata/grammar.pegjs +66 -18
  170. package/libx/odata/index.js +3 -2
  171. package/libx/odata/parser.js +1 -1
  172. package/libx/odata/utils.js +2 -0
  173. package/libx/rest/RestAdapter.js +62 -43
  174. package/libx/rest/middleware/parse.js +2 -1
  175. package/libx/rest/middleware/update.js +1 -1
  176. package/package.json +2 -2
  177. package/server.js +5 -4
  178. package/srv/mtx.cds +1 -1
  179. package/srv/mtx.js +4 -33
  180. package/lib/srv/adapters.js +0 -85
  181. package/lib/utils/resources/index.js +0 -48
  182. package/lib/utils/resources/tar.js +0 -49
  183. package/lib/utils/resources/utils.js +0 -11
  184. package/libx/_runtime/extensibility/activate.js +0 -69
  185. package/libx/_runtime/extensibility/add.js +0 -50
  186. package/libx/_runtime/extensibility/addExtension.js +0 -72
  187. package/libx/_runtime/extensibility/defaults.js +0 -34
  188. package/libx/_runtime/extensibility/handler/transformREAD.js +0 -121
  189. package/libx/_runtime/extensibility/handler/transformRESULT.js +0 -51
  190. package/libx/_runtime/extensibility/handler/transformWRITE.js +0 -64
  191. package/libx/_runtime/extensibility/linter/allowlist_checker.js +0 -373
  192. package/libx/_runtime/extensibility/linter/annotations_checker.js +0 -113
  193. package/libx/_runtime/extensibility/linter/checker_base.js +0 -20
  194. package/libx/_runtime/extensibility/linter/namespace_checker.js +0 -180
  195. package/libx/_runtime/extensibility/linter.js +0 -32
  196. package/libx/_runtime/extensibility/push.js +0 -118
  197. package/libx/_runtime/extensibility/service.js +0 -38
  198. package/libx/_runtime/extensibility/token.js +0 -57
  199. package/libx/_runtime/extensibility/utils.js +0 -131
  200. package/libx/_runtime/extensibility/validation.js +0 -50
  201. package/libx/_runtime/extensibility/views.js +0 -12
  202. package/srv/extensibility-service.cds +0 -60
  203. package/srv/extensibility-service.js +0 -1
  204. package/srv/extensions.cds +0 -8
  205. package/srv/model-provider.cds +0 -61
  206. package/srv/model-provider.js +0 -143
@@ -1,180 +0,0 @@
1
- const Checker = require('./checker_base')
2
-
3
- class NamespaceChecker extends Checker {
4
- static async check(extensionCsn, fullCsn, compileDir, mtxConfig) {
5
- let elementPrefixes = mtxConfig['element-prefix']
6
- let namespaceBlocklist = mtxConfig['namespace-blocklist'] || mtxConfig['namespace-blacklist']
7
- const warnings = []
8
-
9
- if (elementPrefixes) {
10
- if (!Array.isArray(elementPrefixes)) {
11
- elementPrefixes = [elementPrefixes]
12
- }
13
-
14
- if (extensionCsn.extensions) {
15
- // forall switches back to definitions if extensions are undefined
16
- extensionCsn.forall(
17
- () => true,
18
- (element, name, parent) => {
19
- element.name = name // TODO check if bug
20
- this._checkElement(element, parent, elementPrefixes, compileDir, warnings)
21
- },
22
- extensionCsn.extensions
23
- )
24
- }
25
-
26
- extensionCsn.forall(
27
- element => {
28
- return ['entity', 'function', 'action'].includes(element.kind)
29
- },
30
- entity => {
31
- this._checkEntity(entity, extensionCsn, fullCsn, elementPrefixes, compileDir, warnings)
32
- }
33
- )
34
- }
35
-
36
- if (namespaceBlocklist) {
37
- if (!Array.isArray(namespaceBlocklist)) {
38
- namespaceBlocklist = [namespaceBlocklist]
39
- }
40
-
41
- extensionCsn.forall('service', service => {
42
- this._checkNamespace(service, namespaceBlocklist, compileDir, warnings)
43
- })
44
-
45
- extensionCsn.forall(
46
- element => {
47
- return ['aspect', 'entity', 'type'].includes(element.kind)
48
- },
49
- entity => {
50
- if (entity._unresolved) return // skip unresolved entities
51
- this._checkNamespace(entity, namespaceBlocklist, compileDir, warnings)
52
- }
53
- )
54
- }
55
-
56
- return warnings
57
- }
58
-
59
- static _checkElement(element, parent, elementPrefixes, compileDir, warnings) {
60
- if (elementPrefixes.length < 1) {
61
- return
62
- }
63
-
64
- if (!parent) {
65
- return
66
- }
67
-
68
- for (const elementPrefix of elementPrefixes) {
69
- if (!parent.extend || element.name.startsWith(elementPrefix)) {
70
- return
71
- }
72
- }
73
-
74
- warnings.push(this._createPrefixWarning(element, compileDir, elementPrefixes))
75
- }
76
-
77
- static _checkEntity(element, reflectedCsn, reflectedFullCsn, elementPrefixes, compileDir, warnings) {
78
- if (elementPrefixes.length < 1) {
79
- return
80
- }
81
-
82
- if (!this._hasEnclosingEntity(reflectedCsn, element)) {
83
- return
84
- }
85
-
86
- const parent = this._getEnclosingEntity(reflectedCsn, element)
87
-
88
- // parent exists in extension
89
- if (parent) {
90
- return
91
- }
92
-
93
- // check full csn for parent - if it exists, continue
94
- const parentFromFullCsn = this._getEnclosingEntity(reflectedFullCsn, element)
95
- if (!parentFromFullCsn) {
96
- return
97
- }
98
-
99
- // checks nested element - TODO determine real parent and split off parent name
100
- const nestedElementName = this._getNestedEntityName(element) // ,parent
101
-
102
- for (const elementPrefix of elementPrefixes) {
103
- if (nestedElementName.startsWith(elementPrefix)) {
104
- return
105
- }
106
- }
107
-
108
- warnings.push(this._createPrefixWarning(element, compileDir, elementPrefixes))
109
- }
110
-
111
- static _hasEnclosingEntity(reflectedCsn, element) {
112
- const plainEntityName = element.name.replace(reflectedCsn.namespace + '.', '')
113
- const splitEntityName = plainEntityName.split('.')
114
- if (splitEntityName.length > 1) {
115
- return true
116
- }
117
- return false
118
- }
119
-
120
- static _getEnclosingEntity(reflectedCsn, element) {
121
- const splitEntityName = element.name.split('.')
122
- if (splitEntityName.length > 1) {
123
- splitEntityName.pop()
124
- return reflectedCsn.definitions[splitEntityName.join('.')]
125
- }
126
- return null
127
- }
128
-
129
- static _getNestedEntityName(element) {
130
- const splitEntityName = element.name.split('.')
131
- splitEntityName.shift()
132
- return splitEntityName.join('.')
133
- }
134
-
135
- static _checkNamespace(element, namespaceBlacklist, compileDir, warnings) {
136
- for (const namespace of namespaceBlacklist) {
137
- if (element.name.startsWith(namespace)) {
138
- warnings.push(this._createNamespaceWarning(element, compileDir, namespace))
139
- }
140
- }
141
- }
142
-
143
- static _createPrefixWarning(element, compileDir, prefixRule) {
144
- const originFile = this._localizeFile(element.$location.file, compileDir)
145
-
146
- return (
147
- 'Element ' +
148
- element.name +
149
- ' from ' +
150
- originFile +
151
- ' (line:' +
152
- element.$location.line +
153
- ', col:' +
154
- element.$location.col +
155
- ')' +
156
- ' does not adhere to prefix rule: ' +
157
- prefixRule
158
- )
159
- }
160
-
161
- static _createNamespaceWarning(element, compileDir, namespace) {
162
- const originFile = this._localizeFile(element.$location.file, compileDir)
163
-
164
- return (
165
- 'Element ' +
166
- element.name +
167
- ' from ' +
168
- originFile +
169
- ' (line:' +
170
- element.$location.line +
171
- ', col:' +
172
- element.$location.col +
173
- ')' +
174
- ' uses a forbidden namespace: ' +
175
- namespace
176
- )
177
- }
178
- }
179
-
180
- module.exports = NamespaceChecker
@@ -1,32 +0,0 @@
1
- const cds = require('../cds')
2
-
3
- const NamespaceChecker = require('./linter/namespace_checker')
4
- const AnnotationsChecker = require('./linter/annotations_checker')
5
- const AllowlistChecker = require('./linter/allowlist_checker')
6
-
7
- const LINTER_OPTIONS = ['element-prefix', 'extension-allowlist', 'namespace-blocklist']
8
- const LEGACY_OPTIONS = ['entity-whitelist', 'service-whitelist', 'namespace-blacklist']
9
-
10
- const linter = async (extCsn, fullCsn, extensionFilenames, req) => {
11
- const conf = cds.env.requires['cds.xt.ExtensibilityService'] || cds.env.mtx
12
- const compat = cds.env.mtx
13
- const linter_options = {}
14
- let x
15
- for (let p of LINTER_OPTIONS) if ((x = conf[p] || compat[p])) linter_options[p] = x // eslint-disable-line no-cond-assign
16
- for (let p of LEGACY_OPTIONS) if ((x = compat[p])) linter_options[p] = x // eslint-disable-line no-cond-assign
17
- if (!Object.keys(linter_options).length) return
18
-
19
- const reflectedCsn = cds.reflect(extCsn)
20
- const compileBaseDir = global.cds.root
21
- const warnings = await Promise.all([
22
- NamespaceChecker.check(reflectedCsn, fullCsn, compileBaseDir, linter_options),
23
- AnnotationsChecker.check(reflectedCsn, extensionFilenames, compileBaseDir, linter_options),
24
- AllowlistChecker.check(reflectedCsn, fullCsn, extensionFilenames, compileBaseDir, linter_options)
25
- ])
26
- const linterWarnings = [].concat.apply([], warnings) // REVISIT: What are we doing here?
27
- if (linterWarnings.length > 0) {
28
- req.reject(422, linterWarnings[0]) // REVISIT: Why are we returning the first warning only?
29
- }
30
- }
31
-
32
- module.exports = linter
@@ -1,118 +0,0 @@
1
- const fs = require('fs')
2
- const path = require('path')
3
- const cds = require('../cds')
4
-
5
- const activate = require('./activate')
6
- const { collectFiles, getCompilerError } = require('./utils')
7
- const { packTarArchive, unpackTarArchive } = require('../../../lib/utils/resources')
8
- const linter = require('./linter')
9
-
10
- const TEMP_DIR = fs.realpathSync(require('os').tmpdir())
11
- const LOG = cds.log('mtx')
12
-
13
- const _compileProject = async function (extension, req) {
14
- let csn, root, files
15
- try {
16
- root = await fs.promises.mkdtemp(`${TEMP_DIR}${path.sep}extension-`)
17
- await unpackTarArchive(extension, root)
18
- files = collectFiles(root, ['.cds', '.csn']) // REVISIT: don't we have exactly one ext.csn file for all extensions?
19
- csn = await cds.compile(files, { flavor: 'parsed' })
20
- if (csn.requires) delete csn.requires
21
- } catch (err) {
22
- if (err.messages) req.reject(400, getCompilerError(err.messages))
23
- else throw err
24
- } finally {
25
- fs.promises.rm(root, { recursive: true, force: true }).catch(() => {})
26
- }
27
-
28
- return { csn, files }
29
- }
30
-
31
- const base = async function (req) {
32
- const cdsFiles = cds.resolve('*').filter(f => f.startsWith(cds.root) && !f.includes('node_modules'))
33
- const csvObj = await cds.deploy.resources()
34
- const csvFiles = Object.keys(csvObj).filter(f => f.startsWith(cds.root) && !f.includes('node_modules'))
35
- const i18nFiles = collectFiles(cds.root, ['.properties'])
36
- if (req._.res) req._.res.set('content-type', 'application/octet-stream; charset=binary')
37
-
38
- return packTarArchive([...cdsFiles, ...csvFiles, ...i18nFiles], cds.root)
39
- }
40
-
41
- // const _copyFile = async function (file, dir) {
42
- // const destination = path.join(dir, path.relative(cds.root, file))
43
- // const dirname = path.dirname(destination)
44
- // if (!(await exists(dirname))) await fs.promises.mkdir(dirname, { recursive: true })
45
- // await fs.promises.copyFile(file, destination)
46
- // }
47
-
48
- const pull = async function (req) {
49
- LOG.info(`pulling latest model for tenant '${req.tenant}'`)
50
- const { 'cds.xt.ModelProviderService': mps } = cds.services
51
- const csn = await mps.getCsn({
52
- tenant: req.tenant,
53
- toggles: Object.keys(cds.context.features || {}), // with all enabled feature extensions
54
- base: true, // without any custom extensions
55
- flavor: 'xtended'
56
- })
57
- // const csvObj = await cds.deploy.resources()
58
- // const csvFiles = Object.keys(csvObj).filter(f => f.startsWith(cds.root) && !f.includes('node_modules'))
59
- // const i18nFiles = collectFiles(cds.root, ['.properties'])
60
-
61
- req._.res?.set('content-type', 'application/octet-stream; charset=binary')
62
-
63
- let temp, tgz
64
- try {
65
- temp = await fs.promises.mkdtemp(`${TEMP_DIR}${path.sep}extension-`)
66
- await fs.promises.writeFile(path.join(temp, 'index.csn'), cds.compile.to.json(csn))
67
- // for (const file of csvFiles) await _copyFile(file, temp)
68
- // for (const file of i18nFiles) await _copyFile(file, temp)
69
- tgz = await packTarArchive(temp)
70
- } finally {
71
- fs.promises.rm(temp, { recursive: true, force: true }).catch(() => {})
72
- }
73
-
74
- return tgz
75
- }
76
-
77
- const push = async function (req) {
78
- let { extension, tag } = req.data
79
- if (!extension) req.reject(400, 'Missing extension')
80
- const sources = typeof extension === 'string' ? Buffer.from(extension, 'base64') : extension
81
- const { csn: extCsn, files } = await _compileProject(sources, req)
82
- if (!extCsn) req.reject(400, 'Missing or bad extension')
83
- if (!tag) tag = null
84
- const tenant = req.tenant
85
- if (tenant) cds.context = { tenant }
86
-
87
- // remove current extension with tag
88
- if (tag) {
89
- await DELETE.from('cds.xt.Extensions').where({ tag })
90
- }
91
-
92
- LOG.info(`validating extension '${tag}' ...`)
93
- // validation
94
- const { 'cds.xt.ModelProviderService': mps } = cds.services
95
- // REVISIT: Isn't that also done during activate?
96
- const csn = await mps.getCsn(tenant, Object.keys(cds.context.features || {}))
97
- try {
98
- cds.extend(csn).with(extCsn)
99
- } catch (err) {
100
- return req.reject(400, getCompilerError(err.messages))
101
- }
102
- await linter(extCsn, csn, files, req)
103
-
104
- // insert and activate extension
105
- const ID = cds.utils.uuid()
106
- await INSERT.into('cds.xt.Extensions').entries({
107
- ID,
108
- csn: JSON.stringify(extCsn),
109
- sources,
110
- activated: 'database',
111
- tag
112
- })
113
-
114
- LOG.info(`activating extension '${tag}' ...`)
115
- await activate(ID, null, tenant)
116
- }
117
-
118
- module.exports = { base, push, pull }
@@ -1,38 +0,0 @@
1
- const cds = require('../cds')
2
-
3
- const addExtension = require('./addExtension')
4
- const { add, promote } = require('./add')
5
- const { base, push, pull } = require('./push')
6
- const { token } = require('./token')
7
- const { transformExtendedFieldsCREATE, transformExtendedFieldsUPDATE } = require('./handler/transformWRITE')
8
- const { transformExtendedFieldsREAD } = require('./handler/transformREAD')
9
- const { transformExtendedFieldsRESULT } = require('./handler/transformRESULT')
10
-
11
- module.exports = class ExtensibilityService extends cds.ApplicationService {
12
- init() {
13
- this.on('addExtension', addExtension)
14
- this.on('add', add)
15
- this.on('promote', promote)
16
- this.on('base', base)
17
- this.on('push', push)
18
- this.on('pull', pull)
19
-
20
- cds.on('served', () => cds.app.get('/-/cds/login/token', token))
21
-
22
- const { 'cds.xt.ModelProviderService': mps } = cds.services
23
- // REVISIT: mps._in_sidecar -> revisit options
24
- if (!mps?._in_sidecar)
25
- cds.db
26
- .before('CREATE', transformExtendedFieldsCREATE)
27
- .before('UPDATE', transformExtendedFieldsUPDATE)
28
- .before('READ', transformExtendedFieldsREAD)
29
- .after('READ', transformExtendedFieldsRESULT)
30
-
31
- return super.init()
32
- }
33
-
34
- // REVISIT: Do we want to keep this?
35
- get isExtensible() {
36
- return false
37
- }
38
- }
@@ -1,57 +0,0 @@
1
- const { URL } = require('url')
2
- const cds = require('../../../lib')
3
- const LOG = cds.log()
4
-
5
- module.exports = {
6
- async token(request, response) {
7
- if (request.method === 'HEAD') {
8
- response.status(204).send()
9
- return
10
- }
11
-
12
- const { passcode, refresh_token, subdomain, clientid, clientsecret } = request.query
13
- const { credentials } = cds.env.requires.auth
14
- if (!credentials) {
15
- cds.error(
16
- 'No auth credentials defined. The application is likely not bound to an authentication service instance.'
17
- )
18
- }
19
-
20
- const parsedUrl = new URL(credentials.url)
21
- parsedUrl.hostname = subdomain + '.' + parsedUrl.hostname.split('.').slice(1).join('.')
22
-
23
- LOG.info(`Get auth token using URL ${parsedUrl}`)
24
-
25
- if (clientid) {
26
- LOG.info(`Using clientid/clientsecret from API call with clientid ${clientid}`)
27
- }
28
-
29
- const username = clientid ? clientid : credentials.clientid
30
- const password = clientid ? clientsecret : credentials.clientsecret
31
- const { xsappname } = cds.env.requires.auth?.credentials ?? cds.env.requires.uaa?.credentials ?? {}
32
- const path =
33
- (refresh_token
34
- ? `oauth/token?grant_type=refresh_token&refresh_token=${refresh_token}`
35
- : `oauth/token?grant_type=password&passcode=${encodeURIComponent(passcode)}`) +
36
- `&scope=${encodeURIComponent(xsappname + '.cds.ExtensionDeveloper')}`
37
-
38
- try {
39
- const { data } = await require('axios').post(
40
- parsedUrl + path,
41
- { 'Content-Type': 'application/json' },
42
- { auth: { username, password } }
43
- )
44
- response.send(data)
45
- } catch (error) {
46
- const rootCause = error.response?.data ? JSON.stringify(error.response?.data) : error.message
47
- error.message = `Authentication failed with root cause '${rootCause}'. Passcode URL: https://${parsedUrl.hostname}/passcode`
48
- const {
49
- constructor: { name },
50
- message
51
- } = error
52
- const status = name in { JwtRequestError: 1, IncompleteJwtResponseError: 1 } ? 401 : error.response.status ?? 500
53
- LOG.error(message)
54
- response.status(status).send({ message, status })
55
- }
56
- }
57
- }
@@ -1,131 +0,0 @@
1
- const fs = require('fs')
2
- const path = require('path')
3
-
4
- const { ensureNoDraftsSuffix } = require('../common/utils/draft')
5
- const { ensureUnlocalized } = require('../fiori/utils/handler')
6
-
7
- const EXT_BACK_PACK = 'extensions__'
8
-
9
- const getTargetRead = req => {
10
- let name = ''
11
- if (req.query.SELECT.from.join && req.query.SELECT.from.args) {
12
- // join
13
- name = req.query.SELECT.from.args.find(arg => arg.ref && arg.ref[0] !== 'DRAFT.DraftAdministativeData').ref[0]
14
- } else if (req.target.name.SET) {
15
- // union
16
- name = req.target.name.SET.args[0]._target.name
17
- } else {
18
- // simple select
19
- name = req.target.name
20
- }
21
-
22
- return { name: ensureUnlocalized(ensureNoDraftsSuffix(name)) }
23
- }
24
-
25
- const getTargetWrite = (target, model) => {
26
- return model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(target.name))]
27
- }
28
-
29
- const isExtendedEntity = (entityName, model) => {
30
- const entity = model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(entityName))]
31
- if (!entity) return false
32
-
33
- return entity.elements[EXT_BACK_PACK] || Object.values(entity.elements).some(el => el['@cds.extension'])
34
- }
35
-
36
- const _hasExtendedEntityArgs = (args, model) => {
37
- return args.find(arg => {
38
- if (arg.ref) {
39
- return arg.ref[0] !== 'DRAFT.DraftAdministativeData' && isExtendedEntity(arg.ref[0], model)
40
- }
41
-
42
- if (arg.join) {
43
- return _hasExtendedEntityArgs(arg.args, model)
44
- }
45
- })
46
- }
47
-
48
- const _hasExtendedExpand = (columns, targetName, model) => {
49
- for (const col of columns) {
50
- if (col.ref && col.expand) {
51
- const targetNameModel = ensureUnlocalized(ensureNoDraftsSuffix(targetName))
52
- const expTargetName = model.definitions[targetNameModel].elements[col.ref[0]].target
53
- if (isExtendedEntity(expTargetName, model)) return true
54
- _hasExtendedExpand(col.expand, expTargetName, model)
55
- }
56
- }
57
- }
58
-
59
- const hasExtendedEntity = (req, model) => {
60
- if (!req.query.SELECT) return false
61
-
62
- if (req.query.SELECT.columns && req.target && _hasExtendedExpand(req.query.SELECT.columns, req.target.name, model)) {
63
- return true
64
- }
65
-
66
- if (req.query.SELECT.from.join && req.query.SELECT.from.args) {
67
- return _hasExtendedEntityArgs(req.query.SELECT.from.args, model)
68
- }
69
-
70
- if (req.target) {
71
- if (req.target.name.SET) {
72
- return isExtendedEntity(req.target.name.SET.args[0]._target.name, model)
73
- }
74
-
75
- return isExtendedEntity(req.target.name, model)
76
- }
77
- }
78
-
79
- const getExtendedFields = (entityName, model) => {
80
- const elements = model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(entityName))].elements
81
-
82
- return Object.values(elements)
83
- .filter(element => {
84
- return element['@cds.extension']
85
- })
86
- .map(element => {
87
- return element.name
88
- })
89
- }
90
-
91
- const getCompilerError = messages => {
92
- const defaultMsg = 'Error while compiling extension'
93
- if (!messages) return defaultMsg
94
-
95
- for (const msg of messages) {
96
- if (msg.severity === 'Error') return msg.message
97
- }
98
-
99
- return defaultMsg
100
- }
101
-
102
- const collectFiles = (dir, extensions) => {
103
- const files = []
104
- fs.readdirSync(dir).forEach(file => {
105
- const abs = path.join(dir, file)
106
- if (fs.statSync(abs).isDirectory() && !abs.includes('node_modules')) files.push(...collectFiles(abs, extensions))
107
- else if (!extensions || extensions.includes(path.extname(abs))) files.push(abs)
108
- })
109
-
110
- return files
111
- }
112
-
113
- const exists = async fileOrDir => {
114
- try {
115
- return await fs.promises.stat(fileOrDir)
116
- } catch (_) {
117
- return false
118
- }
119
- }
120
-
121
- module.exports = {
122
- EXT_BACK_PACK,
123
- getTargetRead,
124
- getTargetWrite,
125
- isExtendedEntity,
126
- hasExtendedEntity,
127
- getExtendedFields,
128
- getCompilerError,
129
- collectFiles,
130
- exists
131
- }
@@ -1,50 +0,0 @@
1
- const cds = require('../cds')
2
-
3
- const { getCompilerError } = require('./utils')
4
-
5
- const validateCsn = (csn, appCsn, req) => {
6
- if (!csn) req.reject(400, 'Missing extension')
7
- if (!csn.extensions) return
8
-
9
- csn.extensions.forEach(extension => {
10
- if (!extension.extend || !appCsn.definitions[extension.extend]) {
11
- req.reject(400, 'Invalid extension. Parameter "extend" missing or malformed')
12
- }
13
-
14
- if (!extension.elements) {
15
- req.reject(400, 'Invalid extension. Missing parameter "elements"')
16
- }
17
- })
18
- }
19
-
20
- const validateExtensionFields = (csn, appCsn, req) => {
21
- if (!csn.extensions) return
22
-
23
- csn.extensions.forEach(extension => {
24
- if (extension.elements) {
25
- Object.keys(extension.elements).forEach(name => {
26
- if (!/^[A-Za-z]\w*$/.test(name)) {
27
- req.reject(400, `Invalid extension. Bad element name "${name}"`)
28
- }
29
-
30
- if (Object.keys(appCsn.definitions[extension.extend].elements).includes(name)) {
31
- req.reject(400, `Invalid extension. Element "${name}" already exists`)
32
- }
33
- })
34
- }
35
- })
36
- }
37
-
38
- const validateExtension = (ext, csn, req) => {
39
- try {
40
- cds.extend(csn).with(ext)
41
- } catch (err) {
42
- req.reject(400, getCompilerError(err.messages))
43
- }
44
- }
45
-
46
- module.exports = {
47
- validateCsn,
48
- validateExtensionFields,
49
- validateExtension
50
- }
@@ -1,12 +0,0 @@
1
- const _isProjection = target => target && target.query && target.query._target
2
-
3
- const resolveViews = (target, views_ = []) => {
4
- if (_isProjection(target)) {
5
- views_.push(target)
6
- return resolveViews(target.query._target, views_)
7
- }
8
-
9
- return target
10
- }
11
-
12
- module.exports = resolveViews
@@ -1,60 +0,0 @@
1
- // using { cds.xt.TAR } from './model-provider'; //> IMPORTANT: don't add this as it will cause services loaded twice
2
- using { cds.xt.Extensions } from './extensions';
3
-
4
- @protocol: 'rest'
5
- @(requires : 'authenticated-user')
6
- service cds.xt.ExtensibilityService @(path:'/-/cds/extensibility', impl:'@sap/cds/srv/extensibility-service.js') {
7
- // TODO: allow-lists - custom before('add') handler
8
- // TODO: async jobs
9
-
10
- type ActivationLevel : Extensions:activated;
11
- type TAR : LargeBinary;
12
- type CSN : String; // REVISIT: should reuse cds.xt.CSN
13
- type CSN_OR_CDL: String;
14
-
15
- // UIFLEX API
16
- @(requires : ['cds.UIFlexDeveloper', 'cds.ExtensionDeveloper'])
17
- action addExtension(extensions : array of CSN); // REVISIT: change to array of CSN extensions
18
-
19
- // Experimantal API
20
- @(requires : ['cds.ExtensionDeveloper', 'internal-user'])
21
- action add(
22
- extension : CSN_OR_CDL,
23
- tag : Extensions:tag, // optional
24
- activate : ActivationLevel, // optional, default = 'database'
25
- tenant : String // optional, for internal-user only
26
- );
27
-
28
- // Experimantal API
29
- @(requires : ['cds.ExtensionDeveloper', 'internal-user'])
30
- action promote(
31
- tag : Extensions:tag, // optional
32
- activate : ActivationLevel, // optional, default = 'database'
33
- tenant : String // optional, for internal-user only
34
- );
35
-
36
- // REVISIT: consider implementing delete
37
- // action delete(tenant: String, tag: Extensions:tag);
38
-
39
- // EXTENSION PROJECT
40
- // deprecated - mimics current cds extend returning TAR (w/o features)
41
- // only features configured for tenant
42
- @(requires : ['cds.ExtensionDeveloper'])
43
- function base() returns TAR;
44
-
45
- @(requires : ['cds.ExtensionDeveloper'])
46
- action pull() returns TAR;
47
-
48
- @(requires : ['cds.ExtensionDeveloper'])
49
- action push (
50
- extension : LargeBinary, // REVISIT: Using TAR here leads to a strange type check failure
51
- tag : Extensions:tag
52
- // activate : ActivationLevel
53
- );
54
-
55
- // REVISIT: separate action for transport -> later
56
-
57
- event tenantUpdated {
58
- tenant: String;
59
- }
60
- }
@@ -1 +0,0 @@
1
- module.exports = require('../libx/_runtime/extensibility/service')
@@ -1,8 +0,0 @@
1
- entity cds.xt.Extensions {
2
- key ID : UUID;
3
- tag : String; //> e.g.: uiflex, custom, ...
4
- csn : String;
5
- sources : LargeBinary; // TAR
6
- activated : String enum { propertyBag; database };
7
- timestamp : Timestamp @cds.on.insert:$now @cds.on.update:$now; // to support invalidation of models
8
- }