@sap/cds 6.0.4 → 6.1.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 +180 -18
- package/apis/cds.d.ts +11 -7
- package/apis/log.d.ts +124 -0
- package/apis/ql.d.ts +72 -15
- package/apis/services.d.ts +13 -2
- package/bin/build/buildTaskHandler.js +5 -2
- package/bin/build/constants.js +4 -1
- package/bin/build/provider/buildTaskHandlerEdmx.js +11 -39
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +14 -34
- package/bin/build/provider/buildTaskHandlerInternal.js +56 -4
- package/bin/build/provider/buildTaskProviderInternal.js +22 -14
- package/bin/build/provider/hana/index.js +12 -9
- package/bin/build/provider/java/index.js +18 -8
- package/bin/build/provider/mtx/index.js +7 -4
- package/bin/build/provider/mtx/resourcesTarBuilder.js +60 -35
- package/bin/build/provider/mtx-extension/index.js +57 -0
- package/bin/build/provider/mtx-sidecar/index.js +46 -18
- package/bin/build/provider/nodejs/index.js +34 -13
- package/bin/deploy/to-hana/cfUtil.js +7 -2
- package/bin/deploy/to-hana/hana.js +20 -25
- package/bin/deploy/to-hana/hdiDeployUtil.js +13 -2
- package/bin/serve.js +7 -4
- package/lib/compile/{index.js → cds-compile.js} +0 -0
- package/lib/compile/extend.js +15 -5
- package/lib/compile/minify.js +1 -15
- package/lib/compile/parse.js +1 -1
- package/lib/compile/resolve.js +2 -2
- package/lib/compile/to/srvinfo.js +6 -4
- package/lib/{deploy.js → dbs/cds-deploy.js} +9 -8
- package/lib/env/{index.js → cds-env.js} +1 -17
- package/lib/env/{requires.js → cds-requires.js} +24 -3
- package/lib/env/defaults.js +7 -1
- package/lib/env/schemas/cds-package.json +11 -0
- package/lib/env/schemas/cds-rc.json +614 -0
- package/lib/index.js +19 -16
- package/lib/log/{errors.js → cds-error.js} +1 -1
- package/lib/log/{index.js → cds-log.js} +0 -0
- package/lib/log/format/kibana.js +19 -1
- package/lib/ql/Query.js +9 -3
- package/lib/ql/SELECT.js +2 -2
- package/lib/ql/UPDATE.js +2 -2
- package/lib/ql/{index.js → cds-ql.js} +4 -10
- package/lib/req/context.js +49 -17
- package/lib/req/locale.js +5 -1
- package/lib/{serve → srv}/adapters.js +23 -19
- package/lib/{connect → srv}/bindings.js +0 -0
- package/lib/{connect/index.js → srv/cds-connect.js} +1 -1
- package/lib/{serve/index.js → srv/cds-serve.js} +0 -0
- package/lib/{serve → srv}/factory.js +1 -1
- package/lib/{serve/Service-api.js → srv/srv-api.js} +22 -6
- package/lib/{serve/Service-dispatch.js → srv/srv-dispatch.js} +13 -8
- package/lib/{serve/Service-handlers.js → srv/srv-handlers.js} +10 -0
- package/lib/{serve/Service-methods.js → srv/srv-methods.js} +10 -8
- package/lib/srv/srv-models.js +207 -0
- package/lib/{serve/Transaction.js → srv/srv-tx.js} +57 -40
- package/lib/utils/{tests.js → cds-test.js} +2 -2
- package/lib/utils/cds-utils.js +146 -0
- package/lib/utils/index.js +2 -145
- package/lib/utils/jest.js +43 -0
- package/lib/utils/resources/index.js +15 -25
- package/lib/utils/resources/tar.js +18 -41
- package/libx/_runtime/auth/index.js +14 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +7 -19
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +6 -19
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +3 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +0 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +38 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -3
- package/libx/_runtime/cds-services/services/utils/differ.js +4 -0
- package/libx/_runtime/cds-services/util/errors.js +1 -29
- package/libx/_runtime/common/i18n/messages.properties +2 -1
- package/libx/_runtime/common/perf/index.js +10 -15
- package/libx/_runtime/common/utils/binary.js +3 -4
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +0 -1
- package/libx/_runtime/common/utils/entityFromCqn.js +8 -5
- package/libx/_runtime/common/utils/keys.js +14 -6
- package/libx/_runtime/common/utils/resolveView.js +1 -1
- package/libx/_runtime/common/utils/template.js +1 -1
- package/libx/_runtime/db/Service.js +2 -14
- package/libx/_runtime/db/expand/expandCQNToJoin.js +28 -25
- package/libx/_runtime/db/expand/rawToExpanded.js +7 -6
- package/libx/_runtime/db/generic/input.js +8 -1
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +37 -18
- package/libx/_runtime/extensibility/activate.js +47 -47
- package/libx/_runtime/extensibility/add.js +22 -13
- package/libx/_runtime/extensibility/addExtension.js +17 -13
- package/libx/_runtime/extensibility/defaults.js +25 -30
- package/libx/_runtime/extensibility/handler/transformREAD.js +20 -18
- package/libx/_runtime/extensibility/linter/allowlist_checker.js +373 -0
- package/libx/_runtime/extensibility/linter/annotations_checker.js +113 -0
- package/libx/_runtime/extensibility/linter/checker_base.js +20 -0
- package/libx/_runtime/extensibility/linter/namespace_checker.js +180 -0
- package/libx/_runtime/extensibility/linter.js +32 -0
- package/libx/_runtime/extensibility/push.js +77 -20
- package/libx/_runtime/extensibility/service.js +29 -12
- package/libx/_runtime/extensibility/token.js +57 -0
- package/libx/_runtime/extensibility/utils.js +8 -6
- package/libx/_runtime/extensibility/validation.js +6 -9
- package/libx/_runtime/fiori/generic/new.js +0 -11
- package/libx/_runtime/fiori/utils/where.js +1 -1
- package/libx/_runtime/hana/Service.js +0 -1
- package/libx/_runtime/hana/conversion.js +12 -1
- package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +4 -3
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -0
- package/libx/_runtime/hana/pool.js +6 -10
- package/libx/_runtime/hana/search2Contains.js +0 -5
- package/libx/_runtime/hana/search2cqn4sql.js +1 -0
- package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +1 -2
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/messaging/service.js +11 -6
- package/libx/_runtime/remote/utils/data.js +5 -0
- package/libx/_runtime/sqlite/Service.js +7 -6
- package/libx/_runtime/sqlite/execute.js +41 -28
- package/libx/odata/afterburner.js +79 -2
- package/libx/odata/cqn2odata.js +15 -9
- package/libx/odata/grammar.pegjs +157 -76
- package/libx/odata/index.js +9 -3
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +39 -5
- package/libx/rest/RestAdapter.js +3 -7
- package/libx/rest/middleware/delete.js +4 -5
- package/libx/rest/middleware/parse.js +3 -2
- package/package.json +3 -3
- package/server.js +1 -1
- package/srv/extensibility-service.cds +6 -3
- package/srv/model-provider.cds +3 -1
- package/srv/model-provider.js +86 -106
- package/srv/mtx.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +0 -240
|
@@ -0,0 +1,180 @@
|
|
|
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
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
|
@@ -3,29 +3,29 @@ const path = require('path')
|
|
|
3
3
|
const cds = require('../cds')
|
|
4
4
|
|
|
5
5
|
const activate = require('./activate')
|
|
6
|
-
const {
|
|
7
|
-
const { collectFiles, getCompilerError, exists } = require('./utils')
|
|
6
|
+
const { collectFiles, getCompilerError } = require('./utils')
|
|
8
7
|
const { packTarArchive, unpackTarArchive } = require('../../../lib/utils/resources')
|
|
8
|
+
const linter = require('./linter')
|
|
9
9
|
|
|
10
10
|
const TEMP_DIR = fs.realpathSync(require('os').tmpdir())
|
|
11
|
+
const LOG = cds.log('mtx')
|
|
11
12
|
|
|
12
13
|
const _compileProject = async function (extension, req) {
|
|
13
|
-
let csn, root
|
|
14
|
+
let csn, root, files
|
|
14
15
|
try {
|
|
15
16
|
root = await fs.promises.mkdtemp(`${TEMP_DIR}${path.sep}extension-`)
|
|
16
17
|
await unpackTarArchive(extension, root)
|
|
17
|
-
|
|
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' })
|
|
18
20
|
if (csn.requires) delete csn.requires
|
|
19
21
|
} catch (err) {
|
|
20
22
|
if (err.messages) req.reject(400, getCompilerError(err.messages))
|
|
21
23
|
else throw err
|
|
22
24
|
} finally {
|
|
23
|
-
|
|
24
|
-
await (fs.promises.rm || fs.promises.rmdir)(root, { recursive: true, force: true })
|
|
25
|
-
}
|
|
25
|
+
fs.promises.rm(root, { recursive: true, force: true }).catch(() => {})
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
return csn
|
|
28
|
+
return { csn, files }
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
const base = async function (req) {
|
|
@@ -38,24 +38,81 @@ const base = async function (req) {
|
|
|
38
38
|
return packTarArchive([...cdsFiles, ...csvFiles, ...i18nFiles], cds.root)
|
|
39
39
|
}
|
|
40
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
|
+
|
|
41
77
|
const push = async function (req) {
|
|
42
|
-
let { extension } = req.data
|
|
43
|
-
if (!extension
|
|
44
|
-
const
|
|
45
|
-
|
|
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
|
|
46
84
|
const tenant = req.tenant
|
|
47
|
-
|
|
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)
|
|
48
103
|
|
|
104
|
+
// insert and activate extension
|
|
49
105
|
const ID = cds.utils.uuid()
|
|
50
|
-
await cds.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
106
|
+
await INSERT.into('cds.xt.Extensions').entries({
|
|
107
|
+
ID,
|
|
108
|
+
csn: JSON.stringify(extCsn),
|
|
109
|
+
sources,
|
|
110
|
+
activated: 'database',
|
|
111
|
+
tag
|
|
56
112
|
})
|
|
57
113
|
|
|
114
|
+
LOG.info(`activating extension '${tag}' ...`)
|
|
58
115
|
await activate(ID, null, tenant)
|
|
59
116
|
}
|
|
60
117
|
|
|
61
|
-
module.exports = { base, push }
|
|
118
|
+
module.exports = { base, push, pull }
|
|
@@ -2,20 +2,37 @@ const cds = require('../cds')
|
|
|
2
2
|
|
|
3
3
|
const addExtension = require('./addExtension')
|
|
4
4
|
const { add, promote } = require('./add')
|
|
5
|
-
const { base, push } = require('./push')
|
|
5
|
+
const { base, push, pull } = require('./push')
|
|
6
|
+
const { token } = require('./token')
|
|
6
7
|
const { transformExtendedFieldsCREATE, transformExtendedFieldsUPDATE } = require('./handler/transformWRITE')
|
|
7
8
|
const { transformExtendedFieldsREAD } = require('./handler/transformREAD')
|
|
8
9
|
const { transformExtendedFieldsRESULT } = require('./handler/transformRESULT')
|
|
9
10
|
|
|
10
|
-
module.exports =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
.
|
|
18
|
-
|
|
19
|
-
.
|
|
20
|
-
|
|
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
|
+
}
|
|
21
38
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
}
|
|
@@ -8,7 +8,7 @@ const EXT_BACK_PACK = 'extensions__'
|
|
|
8
8
|
|
|
9
9
|
const getTargetRead = req => {
|
|
10
10
|
let name = ''
|
|
11
|
-
if (req.query.SELECT.from.join) {
|
|
11
|
+
if (req.query.SELECT.from.join && req.query.SELECT.from.args) {
|
|
12
12
|
// join
|
|
13
13
|
name = req.query.SELECT.from.args.find(arg => arg.ref && arg.ref[0] !== 'DRAFT.DraftAdministativeData').ref[0]
|
|
14
14
|
} else if (req.target.name.SET) {
|
|
@@ -63,15 +63,17 @@ const hasExtendedEntity = (req, model) => {
|
|
|
63
63
|
return true
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
if (req.query.SELECT.from.join) {
|
|
66
|
+
if (req.query.SELECT.from.join && req.query.SELECT.from.args) {
|
|
67
67
|
return _hasExtendedEntityArgs(req.query.SELECT.from.args, model)
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
if (req.target
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
if (req.target) {
|
|
71
|
+
if (req.target.name.SET) {
|
|
72
|
+
return isExtendedEntity(req.target.name.SET.args[0]._target.name, model)
|
|
73
|
+
}
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
return isExtendedEntity(req.target.name, model)
|
|
76
|
+
}
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
const getExtendedFields = (entityName, model) => {
|
|
@@ -2,12 +2,12 @@ const cds = require('../cds')
|
|
|
2
2
|
|
|
3
3
|
const { getCompilerError } = require('./utils')
|
|
4
4
|
|
|
5
|
-
const validateCsn = (csn, req) => {
|
|
5
|
+
const validateCsn = (csn, appCsn, req) => {
|
|
6
6
|
if (!csn) req.reject(400, 'Missing extension')
|
|
7
7
|
if (!csn.extensions) return
|
|
8
8
|
|
|
9
9
|
csn.extensions.forEach(extension => {
|
|
10
|
-
if (!extension.extend || !
|
|
10
|
+
if (!extension.extend || !appCsn.definitions[extension.extend]) {
|
|
11
11
|
req.reject(400, 'Invalid extension. Parameter "extend" missing or malformed')
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -17,7 +17,7 @@ const validateCsn = (csn, req) => {
|
|
|
17
17
|
})
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const validateExtensionFields = (csn, req) => {
|
|
20
|
+
const validateExtensionFields = (csn, appCsn, req) => {
|
|
21
21
|
if (!csn.extensions) return
|
|
22
22
|
|
|
23
23
|
csn.extensions.forEach(extension => {
|
|
@@ -27,7 +27,7 @@ const validateExtensionFields = (csn, req) => {
|
|
|
27
27
|
req.reject(400, `Invalid extension. Bad element name "${name}"`)
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
if (Object.keys(
|
|
30
|
+
if (Object.keys(appCsn.definitions[extension.extend].elements).includes(name)) {
|
|
31
31
|
req.reject(400, `Invalid extension. Element "${name}" already exists`)
|
|
32
32
|
}
|
|
33
33
|
})
|
|
@@ -35,12 +35,9 @@ const validateExtensionFields = (csn, req) => {
|
|
|
35
35
|
})
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const validateExtension =
|
|
38
|
+
const validateExtension = (ext, csn, req) => {
|
|
39
39
|
try {
|
|
40
|
-
|
|
41
|
-
const csn = await mps.getCsn(tenant, ['*'])
|
|
42
|
-
const extCsn = cds.compile.to.json(ext)
|
|
43
|
-
await cds.compile.to.csn({ 'base.csn': JSON.stringify(csn), 'ext.csn': extCsn })
|
|
40
|
+
cds.extend(csn).with(ext)
|
|
44
41
|
} catch (err) {
|
|
45
42
|
req.reject(400, getCompilerError(err.messages))
|
|
46
43
|
}
|
|
@@ -56,17 +56,6 @@ const _handler = async function (req, next) {
|
|
|
56
56
|
return onDraftActivate(req, next)
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
// fill default values
|
|
60
|
-
const elements = req.target.elements
|
|
61
|
-
for (const column in elements) {
|
|
62
|
-
const col = elements[column]
|
|
63
|
-
if (col.default !== undefined && !(column in DRAFT_COLUMNS_MAP)) {
|
|
64
|
-
if ('val' in col.default) req.data[col.name] = col.default.val
|
|
65
|
-
else if ('ref' in col.default) req.data[col.name] = col.default.ref[0]
|
|
66
|
-
else req.data[col.name] = col.default
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
59
|
const navigationToMany = isNavigationToMany(req)
|
|
71
60
|
|
|
72
61
|
const adminDataCQN = navigationToMany
|
|
@@ -160,7 +160,7 @@ const isActiveEntityRequested = where => {
|
|
|
160
160
|
|
|
161
161
|
while (where[i]) {
|
|
162
162
|
if (where[i].xpr) {
|
|
163
|
-
const isRequested = isActiveEntityRequested(where.xpr)
|
|
163
|
+
const isRequested = isActiveEntityRequested(where[i].xpr)
|
|
164
164
|
if (isRequested) return true
|
|
165
165
|
}
|
|
166
166
|
if (
|
|
@@ -91,7 +91,6 @@ class HanaDatabase extends DatabaseService {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
_registerBeforeHandlers() {
|
|
94
|
-
this._ensureModel && this.before('*', this._ensureModel)
|
|
95
94
|
this.before(['CREATE', 'UPDATE'], '*', this._input) // > has to run before rewrite
|
|
96
95
|
this.before('READ', '*', search) // > has to run before rewrite
|
|
97
96
|
this.before(['CREATE', 'READ', 'UPDATE', 'DELETE'], '*', this._rewrite)
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
2
|
|
|
3
|
+
const driver = require('./driver')
|
|
4
|
+
const isHdb = driver?.name === 'hdb'
|
|
5
|
+
|
|
3
6
|
const convertToBoolean = boolean => {
|
|
4
7
|
if (boolean === null) {
|
|
5
8
|
return null
|
|
@@ -35,7 +38,15 @@ const convertToISONoMillis = element => {
|
|
|
35
38
|
|
|
36
39
|
const convertToString = element => {
|
|
37
40
|
if (element) {
|
|
38
|
-
|
|
41
|
+
if (element instanceof Buffer) {
|
|
42
|
+
if (isHdb && driver.iconv) {
|
|
43
|
+
return driver.iconv.decode(element, 'cesu8')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return Buffer.from(element, 'base64').toString()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return element
|
|
39
50
|
}
|
|
40
51
|
|
|
41
52
|
return null
|
|
@@ -21,12 +21,13 @@ class CustomFunctionBuilder extends FunctionBuilder {
|
|
|
21
21
|
|
|
22
22
|
_handleContains(args) {
|
|
23
23
|
// fuzzy search has three arguments, must not be converted to like expressions
|
|
24
|
-
if (args.length > 2 ||
|
|
24
|
+
if (args.length > 2 || this._options.$searchUsingContains) {
|
|
25
25
|
this._outputObj.sql.push('CONTAINS')
|
|
26
26
|
this._addFunctionArgs(args, true)
|
|
27
|
-
|
|
28
|
-
super._handleContains(args)
|
|
27
|
+
return
|
|
29
28
|
}
|
|
29
|
+
|
|
30
|
+
super._handleContains(args)
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -28,6 +28,11 @@ class CustomSelectBuilder extends SelectBuilder {
|
|
|
28
28
|
return SelectBuilder
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
getDefaultOptions() {
|
|
32
|
+
const options = { $searchUsingContains: !!this._obj.SELECT._$searchUsingContains }
|
|
33
|
+
return { ...super.getDefaultOptions(), ...options }
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
_val(obj) {
|
|
32
37
|
if (typeof obj.val === 'boolean') return { sql: obj.val ? 'true' : 'false', values: [] }
|
|
33
38
|
return super._val(obj)
|
|
@@ -10,13 +10,9 @@ const getError = require('../common/error')
|
|
|
10
10
|
function multiTenantInstanceManager(config = cds.env.requires.db) {
|
|
11
11
|
const { credentials } = config
|
|
12
12
|
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
!(credentials.get_managed_instance_url || credentials.sm_url)
|
|
17
|
-
) {
|
|
18
|
-
throw Object.assign(new Error('No or malformed db credentials'), { credentials: credentials })
|
|
19
|
-
}
|
|
13
|
+
if (!credentials) throw Object.assign(new Error('No database credentials provided'))
|
|
14
|
+
else if (typeof credentials !== 'object' || !(credentials.get_managed_instance_url || credentials.sm_url))
|
|
15
|
+
throw Object.assign(new Error('Malformed database credentials provided'))
|
|
20
16
|
|
|
21
17
|
// new instance manager
|
|
22
18
|
return new Promise((resolve, reject) => {
|
|
@@ -53,9 +49,9 @@ function multiTenantInstanceManager(config = cds.env.requires.db) {
|
|
|
53
49
|
function singleTenantInstanceManager(config = cds.env.requires.db) {
|
|
54
50
|
const { credentials } = config
|
|
55
51
|
|
|
56
|
-
if (!credentials
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
if (!credentials) throw Object.assign(new Error('No database credentials provided'))
|
|
53
|
+
else if (typeof credentials !== 'object' || !credentials.host)
|
|
54
|
+
throw Object.assign(new Error('Malformed database credentials provided'))
|
|
59
55
|
|
|
60
56
|
// mock instance manager
|
|
61
57
|
return {
|
|
@@ -51,11 +51,6 @@ const search2Contains = (cqnSearchPhrase, columns) => {
|
|
|
51
51
|
|
|
52
52
|
const expressionArgs = [{ list: columns }, { val: searchString }]
|
|
53
53
|
|
|
54
|
-
// REVISIT: Mark the expression args with a `_$search` flag, as the `CONTAINS`
|
|
55
|
-
// predicate is not fully supported in the CustomFunctionBuilder class.
|
|
56
|
-
// The `_$search` property is enumerable: false (default), writable: false (default)
|
|
57
|
-
Object.defineProperty(expressionArgs, '_$search', { value: true })
|
|
58
|
-
|
|
59
54
|
const expression = {
|
|
60
55
|
func: 'contains',
|
|
61
56
|
args: expressionArgs
|