@sap/cds 6.1.3 → 6.2.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.
- package/CHANGELOG.md +83 -8
- package/apis/cds.d.ts +18 -6
- package/apis/connect.d.ts +1 -1
- package/apis/cqn.d.ts +1 -1
- package/apis/log.d.ts +23 -5
- package/apis/ql.d.ts +128 -61
- package/apis/services.d.ts +11 -0
- package/apis/test.d.ts +61 -0
- package/apis/utils.d.ts +15 -0
- package/app/fiori/preview.js +1 -0
- package/bin/build/buildTaskEngine.js +70 -22
- package/bin/build/buildTaskFactory.js +18 -11
- package/bin/build/buildTaskHandler.js +1 -1
- package/bin/build/buildTaskProviderFactory.js +3 -13
- package/bin/build/constants.js +0 -1
- package/bin/build/index.js +14 -6
- package/bin/build/provider/buildTaskHandlerEdmx.js +2 -3
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +2 -2
- package/bin/build/provider/buildTaskHandlerInternal.js +3 -6
- package/bin/build/provider/buildTaskProviderInternal.js +51 -39
- package/bin/build/provider/fiori/index.js +3 -3
- package/bin/build/provider/hana/2migration.js +1 -1
- package/bin/build/provider/hana/index.js +34 -27
- package/bin/build/provider/java/index.js +6 -7
- package/bin/build/provider/mtx/index.js +20 -18
- package/bin/build/provider/mtx/resourcesTarBuilder.js +8 -11
- package/bin/build/provider/mtx-sidecar/index.js +13 -17
- package/bin/build/provider/nodejs/index.js +8 -7
- package/bin/build/util.js +22 -4
- package/bin/cds.js +8 -4
- package/bin/deploy/to-hana/cfUtil.js +53 -18
- package/bin/mtx/in-cds.js +1 -0
- package/bin/serve.js +37 -30
- package/lib/auth/basic-auth.js +33 -0
- package/lib/auth/dummy-auth.js +7 -0
- package/lib/auth/ias-auth.js +2 -0
- package/lib/auth/index.js +31 -0
- package/lib/auth/jwt-auth.js +3 -0
- package/lib/auth/mocked-users.js +72 -0
- package/lib/auth/passport-basic.js +12 -0
- package/lib/auth/passport-digest.js +14 -0
- package/lib/auth/xsuaa-auth.js +3 -0
- package/lib/compile/cds-compile.js +3 -3
- package/lib/compile/to/cdl.js +5 -1
- package/lib/compile/to/edm.js +8 -0
- package/lib/compile/to/gql.js +1 -0
- package/lib/compile/to/json.js +30 -5
- package/lib/compile/to/sql.js +3 -1
- package/lib/core/index.js +5 -1
- package/lib/dbs/cds-deploy.js +36 -6
- package/lib/env/cds-env.js +15 -5
- package/lib/env/cds-requires.js +51 -58
- package/lib/env/defaults.js +1 -0
- package/lib/env/schemas/cds-package.json +4 -0
- package/lib/env/schemas/cds-rc.json +63 -77
- package/lib/i18n/localize.js +16 -5
- package/lib/index.js +9 -4
- package/lib/log/cds-error.js +4 -6
- package/lib/log/cds-log.js +89 -53
- package/lib/log/service/index.js +1 -0
- package/lib/ql/CREATE.js +2 -5
- package/lib/ql/DELETE.js +1 -1
- package/lib/ql/DROP.js +1 -3
- package/lib/ql/INSERT.js +3 -3
- package/lib/ql/Query.js +10 -23
- package/lib/ql/SELECT.js +1 -2
- package/lib/ql/UPDATE.js +2 -2
- package/lib/ql/Whereable.js +7 -15
- package/lib/ql/cds-ql.js +9 -3
- package/lib/req/cds-context.js +11 -3
- package/lib/req/context.js +29 -23
- package/lib/req/locale.js +9 -5
- package/lib/req/request.js +1 -0
- package/lib/req/user.js +2 -1
- package/lib/srv/cds-connect.js +1 -1
- package/lib/srv/cds-serve.js +21 -14
- package/lib/srv/middlewares/cds-context.js +29 -0
- package/lib/srv/middlewares/ctx-model.js +24 -0
- package/lib/srv/middlewares/errors.js +9 -0
- package/lib/srv/middlewares/index.js +22 -0
- package/lib/srv/middlewares/sap-statistics.js +13 -0
- package/lib/srv/middlewares/trace.js +102 -0
- package/lib/srv/protocols/_legacy.js +42 -0
- package/lib/srv/protocols/graphql.js +39 -0
- package/lib/srv/protocols/hcql.js +37 -0
- package/lib/srv/protocols/index.js +86 -0
- package/lib/srv/protocols/odata-v2-proxy.js +3767 -0
- package/lib/srv/protocols/odata-v2.js +26 -0
- package/lib/srv/protocols/odata-v4.js +16 -0
- package/lib/srv/protocols/rest.js +13 -0
- package/lib/srv/srv-api.js +5 -0
- package/lib/srv/srv-models.js +4 -6
- package/lib/utils/axios.js +3 -2
- package/lib/utils/cds-test.js +27 -21
- package/lib/utils/cds-utils.js +19 -20
- package/lib/utils/tar.js +175 -0
- package/libx/_runtime/audit/generic/personal/utils.js +18 -7
- package/libx/_runtime/audit/utils/v2.js +1 -0
- package/libx/_runtime/auth/index.js +4 -0
- package/libx/_runtime/auth/strategies/ias-auth.js +76 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +8 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +15 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +9 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriInfo.js +5 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +12 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js +6 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/RequestValidator.js +47 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/util/assert.js +4 -0
- package/libx/_runtime/common/aspects/relation.js +1 -1
- package/libx/_runtime/common/composition/data.js +61 -15
- package/libx/_runtime/common/composition/delete.js +0 -1
- package/libx/_runtime/common/composition/insert.js +0 -1
- package/libx/_runtime/common/composition/tree.js +4 -10
- package/libx/_runtime/common/composition/update.js +44 -21
- package/libx/_runtime/common/generic/auth/capabilities.js +8 -10
- package/libx/_runtime/common/generic/crud.js +1 -2
- package/libx/_runtime/common/generic/etag.js +4 -4
- package/libx/_runtime/common/generic/input.js +4 -4
- package/libx/_runtime/common/generic/paging.js +3 -3
- package/libx/_runtime/common/generic/put.js +3 -3
- package/libx/_runtime/common/generic/sorting.js +4 -4
- package/libx/_runtime/common/generic/temporal.js +3 -3
- package/libx/_runtime/common/i18n/messages.properties +0 -7
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +11 -6
- package/libx/_runtime/common/utils/csn.js +0 -28
- package/libx/_runtime/common/utils/draft.js +8 -1
- package/libx/_runtime/common/utils/path.js +7 -1
- package/libx/_runtime/common/utils/resolveView.js +2 -3
- package/libx/_runtime/db/data-conversion/post-processing.js +3 -44
- package/libx/_runtime/db/generic/input.js +3 -3
- package/libx/_runtime/db/sql-builder/dataTypes.js +4 -0
- package/libx/_runtime/fiori/generic/activate.js +2 -2
- package/libx/_runtime/fiori/generic/before.js +40 -72
- package/libx/_runtime/fiori/generic/cancel.js +2 -2
- package/libx/_runtime/fiori/generic/delete.js +2 -2
- package/libx/_runtime/fiori/generic/edit.js +2 -2
- package/libx/_runtime/fiori/generic/new.js +2 -2
- package/libx/_runtime/fiori/generic/patch.js +49 -37
- package/libx/_runtime/fiori/generic/prepare.js +2 -2
- package/libx/_runtime/fiori/generic/read.js +27 -37
- package/libx/_runtime/fiori/utils/where.js +4 -2
- package/libx/_runtime/hana/Service.js +1 -3
- package/libx/_runtime/hana/conversion.js +3 -0
- package/libx/_runtime/hana/driver.js +33 -3
- package/libx/_runtime/hana/dynatrace.js +1 -0
- package/libx/_runtime/hana/search2Contains.js +12 -1
- package/libx/_runtime/hana/search2cqn4sql.js +10 -27
- package/libx/_runtime/hana/streaming.js +1 -0
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +1 -0
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +5 -2
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -0
- package/libx/_runtime/messaging/enterprise-messaging.js +62 -3
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/messaging/redis-messaging.js +1 -0
- package/libx/_runtime/remote/Service.js +2 -2
- package/libx/_runtime/remote/utils/client.js +8 -3
- package/libx/_runtime/remote/utils/data.js +7 -2
- package/libx/_runtime/sqlite/Service.js +18 -7
- package/libx/_runtime/sqlite/conversion.js +3 -0
- package/libx/_runtime/sqlite/convertAssocToOneManaged.js +3 -3
- package/libx/_runtime/sqlite/localized.js +8 -8
- package/libx/odata/afterburner.js +39 -7
- package/libx/odata/cqn2odata.js +6 -3
- package/libx/odata/grammar.pegjs +66 -18
- package/libx/odata/index.js +3 -2
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +2 -0
- package/libx/rest/RestAdapter.js +62 -43
- package/libx/rest/middleware/parse.js +2 -1
- package/libx/rest/middleware/update.js +1 -1
- package/package.json +2 -2
- package/server.js +5 -4
- package/srv/mtx.cds +1 -1
- package/srv/mtx.js +4 -33
- package/lib/srv/adapters.js +0 -85
- package/lib/utils/resources/index.js +0 -48
- package/lib/utils/resources/tar.js +0 -49
- package/lib/utils/resources/utils.js +0 -11
- package/libx/_runtime/extensibility/activate.js +0 -69
- package/libx/_runtime/extensibility/add.js +0 -50
- package/libx/_runtime/extensibility/addExtension.js +0 -72
- package/libx/_runtime/extensibility/defaults.js +0 -34
- package/libx/_runtime/extensibility/handler/transformREAD.js +0 -121
- package/libx/_runtime/extensibility/handler/transformRESULT.js +0 -51
- package/libx/_runtime/extensibility/handler/transformWRITE.js +0 -64
- package/libx/_runtime/extensibility/linter/allowlist_checker.js +0 -373
- package/libx/_runtime/extensibility/linter/annotations_checker.js +0 -113
- package/libx/_runtime/extensibility/linter/checker_base.js +0 -20
- package/libx/_runtime/extensibility/linter/namespace_checker.js +0 -180
- package/libx/_runtime/extensibility/linter.js +0 -32
- package/libx/_runtime/extensibility/push.js +0 -118
- package/libx/_runtime/extensibility/service.js +0 -38
- package/libx/_runtime/extensibility/token.js +0 -57
- package/libx/_runtime/extensibility/utils.js +0 -131
- package/libx/_runtime/extensibility/validation.js +0 -50
- package/libx/_runtime/extensibility/views.js +0 -12
- package/srv/extensibility-service.cds +0 -60
- package/srv/extensibility-service.js +0 -1
- package/srv/extensions.cds +0 -8
- package/srv/model-provider.cds +0 -61
- 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')
|
package/srv/extensions.cds
DELETED
|
@@ -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
|
-
}
|