@sap/cds 6.0.3 → 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +165 -18
- package/apis/cds.d.ts +11 -7
- package/apis/log.d.ts +46 -0
- package/apis/ql.d.ts +72 -15
- 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/ql/Query.js +9 -3
- package/lib/ql/SELECT.js +2 -2
- package/lib/ql/{index.js → cds-ql.js} +0 -9
- 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} +1 -1
- 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 +1 -4
- 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/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/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 +56 -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/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/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 +9 -7
- 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 +84 -104
- package/srv/mtx.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +0 -240
|
@@ -2,7 +2,7 @@ const fs = require('fs')
|
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const BuildTaskHandler = require('../buildTaskHandler')
|
|
4
4
|
const { hasOptionValue } = require('../util')
|
|
5
|
-
const { FOLDER_GEN } = require('../constants')
|
|
5
|
+
const { FOLDER_GEN, BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY } = require('../constants')
|
|
6
6
|
|
|
7
7
|
class BuildTaskHandlerInternal extends BuildTaskHandler {
|
|
8
8
|
/**
|
|
@@ -47,7 +47,7 @@ class BuildTaskHandlerInternal extends BuildTaskHandler {
|
|
|
47
47
|
// the build results have already been deleted by the BuildTaskEngine if the build.target !== '.'
|
|
48
48
|
// make sure that src is not a subfolder of dest
|
|
49
49
|
if (this._buildOptions.root === this._buildOptions.target && this.task.src !== this.task.dest && !this._isSubDirectory(this.task.dest, this.task.src)) {
|
|
50
|
-
await fs.promises.rm(this.task.dest, {force: true, recursive: true})
|
|
50
|
+
await fs.promises.rm(this.task.dest, { force: true, recursive: true })
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -164,8 +164,60 @@ class BuildTaskHandlerInternal extends BuildTaskHandler {
|
|
|
164
164
|
)
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
async compileToJson(model, csnFile) {
|
|
168
|
+
// This will als add a @source prop containing the relative path to the origin .cds source file
|
|
169
|
+
// and a parsed _where clause for @restrict.{grant,where} annotations.
|
|
170
|
+
// The @source annotation is required for correct custom handler resolution if no @impl annotation has been defined as
|
|
171
|
+
// custom service handler implementations are relative to the origin .cds source files.
|
|
172
|
+
// For staging builds (task.src !== task.dest) the csn.json file that is served at runtime is copied into a corresponding srv subfolder.
|
|
173
|
+
// As a consequence the src folder name has to be included in the @source file name while for inplace builds (task.src === task.dest) this is not the case.
|
|
174
|
+
// This ensures that the paths are relative to the cwd when executing cds run.
|
|
175
|
+
const jsonOptions = {
|
|
176
|
+
cwd: this.buildOptions.root,
|
|
177
|
+
src: this.task.src === this.task.dest ? this.task.src : this.buildOptions.root
|
|
178
|
+
}
|
|
179
|
+
const csnStr = this.cds.compile.to.json(model, jsonOptions)
|
|
180
|
+
if (!this.hasBuildOption(BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY)) {
|
|
181
|
+
await this.write(csnStr).to(csnFile)
|
|
182
|
+
}
|
|
183
|
+
return csnStr
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Collect and write language bundles into a single i18n.json file.
|
|
188
|
+
* @param {Object} model
|
|
189
|
+
* @param {string} bundleDest
|
|
190
|
+
*/
|
|
191
|
+
async collectLanguageBundles(model, bundleDest) {
|
|
192
|
+
// collect effective i18n properties...
|
|
193
|
+
let bundles = {}
|
|
194
|
+
const bundleGenerator = this.cds.localize.bundles4(model)
|
|
195
|
+
if (bundleGenerator && bundleGenerator[Symbol.iterator]) {
|
|
196
|
+
for (let [locale, bundle] of bundleGenerator) {
|
|
197
|
+
// fallback bundle has the name ""
|
|
198
|
+
if (typeof locale === 'string') {
|
|
199
|
+
bundles[locale] = bundle
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// omit bundles in case the fallback bundle is the only existing entry
|
|
205
|
+
const keys = Object.keys(bundles)
|
|
206
|
+
if (keys.length === 1 && keys[0] === "" && Object.keys(bundles[keys[0]]).length === 0) {
|
|
207
|
+
bundles = {}
|
|
208
|
+
}
|
|
209
|
+
// copied from ../compile/i18n.js
|
|
210
|
+
const { folders = ['i18n'], file: base = 'i18n' } = this.env.i18n
|
|
211
|
+
const file = path.join(bundleDest, folders[0], base + '.json')
|
|
212
|
+
|
|
213
|
+
// bundleDest might be null
|
|
214
|
+
if (bundleDest && Object.keys(bundles).length > 0) {
|
|
215
|
+
if (!this.hasBuildOption(BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY)) {
|
|
216
|
+
await this.write(bundles).to(file)
|
|
217
|
+
}
|
|
218
|
+
return { file, bundles }
|
|
219
|
+
}
|
|
220
|
+
return null
|
|
169
221
|
}
|
|
170
222
|
|
|
171
223
|
_isSubDirectory(parent, child) {
|
|
@@ -4,7 +4,7 @@ const { hasJavaNature, getProperty, isStreamlinedMtx } = require('../util')
|
|
|
4
4
|
const BuildTaskProvider = require('../buildTaskProvider')
|
|
5
5
|
|
|
6
6
|
const { FILE_EXT_CDS, BUILD_TASK_HANA, BUILD_TASK_FIORI, BUILD_TASK_JAVA, BUILD_TASK_JAVA_CF, BUILD_TASK_NODEJS, BUILD_TASK_NODE_CF, BUILD_TASK_MTX,
|
|
7
|
-
BUILD_TASK_PREFIX, BUILD_TASKS, BUILD_TASK_MTX_SIDECAR, MTX_SIDECAR_FOLDER } = require("../constants")
|
|
7
|
+
BUILD_TASK_PREFIX, BUILD_TASKS, BUILD_TASK_MTX_SIDECAR, MTX_SIDECAR_FOLDER, BUILD_TASK_MTX_EXTENSION } = require("../constants")
|
|
8
8
|
|
|
9
9
|
class BuildTaskProviderInternal extends BuildTaskProvider {
|
|
10
10
|
constructor(cds, logger) {
|
|
@@ -57,6 +57,7 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
|
|
|
57
57
|
case BUILD_TASK_FIORI:
|
|
58
58
|
task.src = BuildTaskProviderInternal._normalizePath(this.env.folders.app)
|
|
59
59
|
break
|
|
60
|
+
case BUILD_TASK_MTX_EXTENSION:
|
|
60
61
|
case BUILD_TASK_MTX:
|
|
61
62
|
task.src = "."
|
|
62
63
|
break
|
|
@@ -112,12 +113,9 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
|
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
} else {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (!srv) {
|
|
119
|
-
this.logger.log(`project doesn't have a service module '${this.env.folders.srv}'`)
|
|
120
|
-
}
|
|
116
|
+
!db && this.logger._debug && this.logger.debug(`project doesn't have a database module '${this.env.folders.db}'`)
|
|
117
|
+
!srv && this.logger._debug && this.logger.debug(`project doesn't have a service module '${this.env.folders.srv}'`)
|
|
118
|
+
|
|
121
119
|
// create hana build task
|
|
122
120
|
const dbTask = this._createDbTask(projectPath, db, dbOptions, buildOptions)
|
|
123
121
|
if (dbTask) {
|
|
@@ -138,7 +136,7 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
|
|
|
138
136
|
|
|
139
137
|
_createDbTask(projectPath, src, taskOptions, buildOptions) {
|
|
140
138
|
this.logger.debug("determining database kind.")
|
|
141
|
-
if (!src) {
|
|
139
|
+
if (!src || BuildTaskProviderInternal._isExtension(this.env)) {
|
|
142
140
|
return null
|
|
143
141
|
}
|
|
144
142
|
let task = null
|
|
@@ -190,7 +188,7 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
|
|
|
190
188
|
if (this.env.requires["cds.xt.ModelProviderService"]) {
|
|
191
189
|
// sidecar scenario for Nodejs or Java
|
|
192
190
|
const sidecarEnv = this.env.for("cds", path.join(projectPath, MTX_SIDECAR_FOLDER))
|
|
193
|
-
if (
|
|
191
|
+
if (BuildTaskProviderInternal._isSidecar(sidecarEnv)) {
|
|
194
192
|
task = {
|
|
195
193
|
for: BUILD_TASK_MTX_SIDECAR
|
|
196
194
|
}
|
|
@@ -205,6 +203,12 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
|
|
|
205
203
|
}
|
|
206
204
|
this.logger.debug("Nodejs MTX scenario without sidecar")
|
|
207
205
|
}
|
|
206
|
+
} else if (BuildTaskProviderInternal._isExtension(this.env)) {
|
|
207
|
+
// streamlined mtx extension project
|
|
208
|
+
task = {
|
|
209
|
+
for: BUILD_TASK_MTX_EXTENSION
|
|
210
|
+
}
|
|
211
|
+
this.logger.debug("Streamlined MTX extension")
|
|
208
212
|
} else if (this.env.get("requires.multitenancy") && isNode) {
|
|
209
213
|
// classic mtx
|
|
210
214
|
task = {
|
|
@@ -219,11 +223,11 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
|
|
|
219
223
|
|
|
220
224
|
_createSrvTask(projectPath, src, taskOptions) {
|
|
221
225
|
this.logger.debug("determining implementation technology")
|
|
222
|
-
if (!src) {
|
|
226
|
+
if (!src || BuildTaskProviderInternal._isExtension(this.env)) {
|
|
223
227
|
return null
|
|
224
228
|
}
|
|
225
229
|
let task = null
|
|
226
|
-
if (
|
|
230
|
+
if (BuildTaskProviderInternal._hasJavaNature(projectPath, src)) {
|
|
227
231
|
task = this._createJavaTask(projectPath, src, taskOptions)
|
|
228
232
|
} else {
|
|
229
233
|
task = this._createNodeTask(src, taskOptions)
|
|
@@ -255,8 +259,12 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
|
|
|
255
259
|
}
|
|
256
260
|
}
|
|
257
261
|
|
|
258
|
-
_isSidecar(env) {
|
|
259
|
-
return env.requires['cds.xt.ModelProviderService']?.
|
|
262
|
+
static _isSidecar(env) {
|
|
263
|
+
return env.requires['cds.xt.ModelProviderService']?.kind === "in-sidecar"
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
static _isExtension(env) {
|
|
267
|
+
return !!env.extends
|
|
260
268
|
}
|
|
261
269
|
|
|
262
270
|
/**
|
|
@@ -264,7 +272,7 @@ class BuildTaskProviderInternal extends BuildTaskProvider {
|
|
|
264
272
|
* @param {string} projectPath - the absolute project path
|
|
265
273
|
* @param {string} src - the folder name of the service module
|
|
266
274
|
*/
|
|
267
|
-
_hasJavaNature(projectPath, src) {
|
|
275
|
+
static _hasJavaNature(projectPath, src) {
|
|
268
276
|
return hasJavaNature([path.join(projectPath, src), projectPath])
|
|
269
277
|
}
|
|
270
278
|
|
|
@@ -22,6 +22,8 @@ const FILE_NAME_PACKAGE_JSON = "package.json"
|
|
|
22
22
|
const PATH_LAST_DEV_CSN = "last-dev/csn.json"
|
|
23
23
|
const FILE_NAME_UNDEPLOY_JSON = "undeploy.json"
|
|
24
24
|
|
|
25
|
+
const DEPLOY_FORMAT = "deploy-format"
|
|
26
|
+
|
|
25
27
|
// add well-known types supported by HANA Cloud Edition - see also https://github.wdf.sap.corp/cap/issues/issues/8056
|
|
26
28
|
const REQUIRED_PLUGIN_TYPES = [FILE_EXT_CSV, FILE_EXT_HDBTABLEDATA, FILE_EXT_HDBTABLE, ".hdbview", ".hdbindex", ".hdbconstraint"]
|
|
27
29
|
class HanaModuleBuilder extends BuildTaskHandlerInternal {
|
|
@@ -91,22 +93,24 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
|
|
|
91
93
|
/**
|
|
92
94
|
* Copies all files located at <src> (except HANA artefacts not contained in <db>/src/**) to the folder <dest>.
|
|
93
95
|
* '*.csv' files are read based on the corresponding CDS model file location and copied as flat list into folder '<dest>/src/gen>'.
|
|
94
|
-
*
|
|
95
|
-
* @param {string} src
|
|
96
|
-
* @param {string} dest
|
|
97
|
-
* @param {Object} model
|
|
96
|
+
*
|
|
97
|
+
* @param {string} src
|
|
98
|
+
* @param {string} dest
|
|
99
|
+
* @param {Object} model
|
|
98
100
|
*/
|
|
99
101
|
async _copyResourcesExt(src, dest, model) {
|
|
100
|
-
const
|
|
101
|
-
const resources = Object.keys(await deploy.resources(model))
|
|
102
|
+
const resources = Object.keys(await this.cds.deploy.resources(model))
|
|
102
103
|
const csvPath = path.join(src, "data")
|
|
103
104
|
// determine subfolder name used by the application for backward compatibility
|
|
104
105
|
const csvFolder = resources.some(res => res && res.startsWith(csvPath)) ? "data" : "csv"
|
|
105
106
|
|
|
106
107
|
// 1. copy csv files into 'src/gen/data' or 'src/gen/csv' subfolder
|
|
107
108
|
const promises = []
|
|
109
|
+
const dbSrc = path.join(src, 'src')
|
|
110
|
+
|
|
108
111
|
resources.forEach(res => {
|
|
109
|
-
|
|
112
|
+
// do not duplicate resources that are already contained in db/src/**
|
|
113
|
+
if (res && /\.csv$/.test(res) && !res.startsWith(dbSrc)) {
|
|
110
114
|
promises.push(this.copy(res).to(path.join(this.task.options.compileDest, csvFolder, path.basename(res))))
|
|
111
115
|
}
|
|
112
116
|
})
|
|
@@ -117,7 +121,6 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
|
|
|
117
121
|
blockList += this.hasBuildOption(CONTENT_ENV, false) ? "|\\.env($|\\..*$)" : ""
|
|
118
122
|
blockList += this.hasBuildOption(CONTENT_DEFAULT_ENV_JSON, false) ? "|default-env\\.json$" : ""
|
|
119
123
|
blockList = new RegExp(blockList)
|
|
120
|
-
const dbSrc = path.join(src, 'src')
|
|
121
124
|
|
|
122
125
|
await this.copyNativeContent(src, dest, (entry) => {
|
|
123
126
|
if (entry.startsWith(dbSrc)) {
|
|
@@ -241,7 +244,7 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
|
|
|
241
244
|
const pluginTypes = new Set([...REQUIRED_PLUGIN_TYPES, ...undeployTypes])
|
|
242
245
|
|
|
243
246
|
// compile to old format (.hdbcds) or new format (.hdbtable / .hdbview)
|
|
244
|
-
const format = this.env.get("requires.db.
|
|
247
|
+
const format = this.getBuildOption(DEPLOY_FORMAT) || this.env.get("requires.db." + DEPLOY_FORMAT) || this.env.get("hana." + DEPLOY_FORMAT)
|
|
245
248
|
if (!this.cds.compile.to[format]) {
|
|
246
249
|
return Promise.reject(new Error(`Invalid deploy-format defined: ${format}`))
|
|
247
250
|
}
|
|
@@ -4,12 +4,12 @@ const path = require('path')
|
|
|
4
4
|
const BuildTaskHandlerEdmx = require('../buildTaskHandlerEdmx')
|
|
5
5
|
const { isOldJavaStack, BuildError } = require('../../util')
|
|
6
6
|
|
|
7
|
-
const { BUILD_OPTION_OUTPUT_MODE, ODATA_VERSION, ODATA_VERSION_V2, OUTPUT_MODE_RESULT_ONLY, FILE_EXT_CDS, SKIP_ASSERT_COMPILER_V2, CONTENT_LANGUAGE_BUNDLES, CONTENT_DEFAULT_CSN } = require('../../constants')
|
|
7
|
+
const { BUILD_OPTION_OUTPUT_MODE, ODATA_VERSION, ODATA_VERSION_V2, OUTPUT_MODE_RESULT_ONLY, FILE_EXT_CDS, SKIP_ASSERT_COMPILER_V2, CONTENT_LANGUAGE_BUNDLES, CONTENT_DEFAULT_CSN, DEFAULT_CSN_FILE_NAME } = require('../../constants')
|
|
8
8
|
const { INFO } = require('../../buildTaskHandler')
|
|
9
9
|
|
|
10
10
|
const DEFAULT_COMPILE_DEST_FOLDER = path.normalize('src/main/resources/edmx')
|
|
11
11
|
|
|
12
|
-
class
|
|
12
|
+
class JavaModuleBuilder extends BuildTaskHandlerEdmx {
|
|
13
13
|
init() {
|
|
14
14
|
super.init()
|
|
15
15
|
this.task.options.compileDest = path.resolve(this.task.dest, this.task.options.compileDest || DEFAULT_COMPILE_DEST_FOLDER)
|
|
@@ -48,7 +48,10 @@ class JavaCfModuleBuilder extends BuildTaskHandlerEdmx {
|
|
|
48
48
|
|
|
49
49
|
if (this.hasBuildOption(CONTENT_LANGUAGE_BUNDLES, true)) {
|
|
50
50
|
// collect and write language bundles into single i18n.json file
|
|
51
|
-
await this.collectLanguageBundles(model, this.task.dest)
|
|
51
|
+
const i18n = await this.collectLanguageBundles(model, this.task.dest)
|
|
52
|
+
if (i18n) {
|
|
53
|
+
this._result.languageBundles = i18n.bundles
|
|
54
|
+
}
|
|
52
55
|
}
|
|
53
56
|
if (!this.hasBuildOption(BUILD_OPTION_OUTPUT_MODE, OUTPUT_MODE_RESULT_ONLY)) {
|
|
54
57
|
await this._copyNativeContent(src, dest)
|
|
@@ -62,7 +65,7 @@ class JavaCfModuleBuilder extends BuildTaskHandlerEdmx {
|
|
|
62
65
|
return
|
|
63
66
|
}
|
|
64
67
|
this.logger._debug && this.logger.debug(`Deleting build target folder ${this.task.options.compileDest}`)
|
|
65
|
-
await fs.promises.rm(this.task.options.compileDest, {force: true, recursive: true})
|
|
68
|
+
await fs.promises.rm(this.task.options.compileDest, { force: true, recursive: true })
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
async _copyNativeContent(src, dest) {
|
|
@@ -82,13 +85,20 @@ class JavaCfModuleBuilder extends BuildTaskHandlerEdmx {
|
|
|
82
85
|
...this._options4odata(),
|
|
83
86
|
...compileOptions
|
|
84
87
|
})
|
|
85
|
-
|
|
88
|
+
const csnFile = path.join(csnDest, DEFAULT_CSN_FILE_NAME)
|
|
89
|
+
let csnModel
|
|
86
90
|
// adding csn to build result containing @source and _where persisted properties
|
|
87
91
|
if (this.hasBuildOption(CONTENT_DEFAULT_CSN, true)) { //default true or undefined
|
|
88
|
-
await this.compileToJson(model,
|
|
92
|
+
const csnStr = await this.compileToJson(model, csnFile)
|
|
93
|
+
csnModel = JSON.parse(csnStr)
|
|
94
|
+
csnModel.meta = model.meta
|
|
89
95
|
} else {
|
|
90
|
-
await this.compileToJson(m,
|
|
96
|
+
const csnStr = await this.compileToJson(m, csnFile)
|
|
97
|
+
csnModel = JSON.parse(csnStr)
|
|
98
|
+
csnModel.meta = m.meta
|
|
91
99
|
}
|
|
100
|
+
this._result.csn = csnModel
|
|
101
|
+
|
|
92
102
|
return m
|
|
93
103
|
}
|
|
94
104
|
|
|
@@ -98,4 +108,4 @@ class JavaCfModuleBuilder extends BuildTaskHandlerEdmx {
|
|
|
98
108
|
return match && match[1] === 1
|
|
99
109
|
}
|
|
100
110
|
}
|
|
101
|
-
module.exports =
|
|
111
|
+
module.exports = JavaModuleBuilder
|
|
@@ -47,7 +47,7 @@ class StreamlinedMtxBuilder {
|
|
|
47
47
|
if (!model) {
|
|
48
48
|
return
|
|
49
49
|
}
|
|
50
|
-
return new ResourcesTarProvider(this._handler).
|
|
50
|
+
return new ResourcesTarProvider(this._handler).createTar(dest, model)
|
|
51
51
|
}
|
|
52
52
|
async clean() {
|
|
53
53
|
// staging build content is deleted by BuildTaskEngine
|
|
@@ -96,8 +96,11 @@ class ClassicMtxBuilder {
|
|
|
96
96
|
}))
|
|
97
97
|
|
|
98
98
|
// collect and write language bundles into single i18n.json file
|
|
99
|
-
await this._handler.collectLanguageBundles(model, destSdc)
|
|
100
|
-
|
|
99
|
+
const i18n = await this._handler.collectLanguageBundles(model, destSdc)
|
|
100
|
+
if (i18n) {
|
|
101
|
+
this._handler._result.languageBundles = i18n.bundles
|
|
102
|
+
}
|
|
103
|
+
|
|
101
104
|
// copy native hana content and templates
|
|
102
105
|
await this._copyNativeContent(this._handler.task.src, destSdc, tenantDbPath)
|
|
103
106
|
}
|
|
@@ -172,7 +175,7 @@ class ClassicMtxBuilder {
|
|
|
172
175
|
function isValid(e, pattern, nsPattern, kind) {
|
|
173
176
|
return e && e.name && (e.name === pattern || (nsPattern && e.name.startsWith(nsPattern))) && (!kind || e.kind === kind)
|
|
174
177
|
}
|
|
175
|
-
|
|
178
|
+
|
|
176
179
|
if (extensionAllowlist || entityWhitelist || serviceWhitelist) {
|
|
177
180
|
const invalidEntries = new Set()
|
|
178
181
|
const reflected = this._handler.cds.reflect(model)
|
|
@@ -1,52 +1,77 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
2
1
|
const path = require('path')
|
|
3
2
|
const BuildTaskHandlerInternal = require('../buildTaskHandlerInternal')
|
|
4
3
|
|
|
5
4
|
const { BUILD_TASK_HANA } = require('../../constants')
|
|
6
|
-
const { WARNING
|
|
7
|
-
const
|
|
5
|
+
const { WARNING } = BuildTaskHandlerInternal
|
|
6
|
+
const DEFAULT_TAR_NAME = "resources.tgz"
|
|
8
7
|
|
|
9
8
|
class ResourcesTarBuilder {
|
|
10
9
|
constructor(handler) {
|
|
11
10
|
this._handler = handler
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
let resourceRoot = await this._getHanaTenantDbDest()
|
|
16
|
-
let files
|
|
17
|
-
if (resourceRoot) {
|
|
18
|
-
const hanaSrcFolder = path.join(resourceRoot, 'src')
|
|
19
|
-
const undeployJson = path.join(resourceRoot, 'undeploy.json')
|
|
13
|
+
get handler() { return this._handler }
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return true
|
|
28
|
-
}
|
|
29
|
-
return false
|
|
30
|
-
})
|
|
31
|
-
} else {
|
|
32
|
-
resourceRoot = path.join(this._handler.buildOptions.root, this._handler.env.folders.db)
|
|
33
|
-
const deploy = require('../../../../lib/deploy')
|
|
34
|
-
files = Object.keys(await deploy.resources(model))
|
|
35
|
-
}
|
|
36
|
-
if (files.length > 0) {
|
|
37
|
-
const { packTarArchive } = require('../../../../lib/utils/resources')
|
|
38
|
-
return packTarArchive(files, resourceRoot, false)
|
|
39
|
-
} else {
|
|
40
|
-
this._handler.pushMessage("No deployment resources found - skip resource.tgz", WARNING)
|
|
15
|
+
async createTar(dest, model) {
|
|
16
|
+
const { root, files } = await this._getResources(model)
|
|
17
|
+
if (files.length === 0) {
|
|
18
|
+
// packTarArchive failes otherwise
|
|
19
|
+
this.handler.pushMessage("No deployment resources found - skip resources.tgz", WARNING)
|
|
20
|
+
return
|
|
41
21
|
}
|
|
22
|
+
await this.writeTarFile(files, root, path.join(dest, DEFAULT_TAR_NAME))
|
|
42
23
|
}
|
|
43
24
|
|
|
44
|
-
async
|
|
45
|
-
const
|
|
25
|
+
async writeTarFile(files, root, tarFile) {
|
|
26
|
+
const { packTarArchive } = require('../../../../lib/utils/resources')
|
|
27
|
+
const buffer = await packTarArchive(files, root)
|
|
46
28
|
if (buffer) {
|
|
47
|
-
await this.
|
|
29
|
+
await this.handler.write(buffer).to(tarFile)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async _getResources(model) {
|
|
34
|
+
// distinguish HANA and SQLITE deployment
|
|
35
|
+
let root = await this._getHanaTenantDbDest()
|
|
36
|
+
let files
|
|
37
|
+
if (root) {
|
|
38
|
+
files = this._getHanaResources(root)
|
|
39
|
+
} else {
|
|
40
|
+
root = path.join(this.handler.buildOptions.root, this.handler.env.folders.db)
|
|
41
|
+
files = await this._getSqliteResources(model)
|
|
48
42
|
}
|
|
49
|
-
|
|
43
|
+
return { root, files }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Reads all resources for HANA deployment - 'db/src/**' and 'db/undeploy.json'.
|
|
48
|
+
* - CSV files, native HANA artefacts, generated HANA artefacts, undeploy.json
|
|
49
|
+
* See '../../../../lib.deploy'
|
|
50
|
+
* Note: the hana build task has already been executed
|
|
51
|
+
* @param {string} root
|
|
52
|
+
* @returns an array containing all resources
|
|
53
|
+
*/
|
|
54
|
+
_getHanaResources(root) {
|
|
55
|
+
const hanaNativeFolders = [path.join(root, 'src'), path.join(root, 'cfg')]
|
|
56
|
+
const hanaNativeFiles = [path.join(root, 'undeploy.json'), path.join(root, '.hdiignore')]
|
|
57
|
+
|
|
58
|
+
return BuildTaskHandlerInternal._find(root, (res) => {
|
|
59
|
+
if (hanaNativeFolders.some(folder => res.startsWith(folder)) || hanaNativeFiles.some(file => res === file)) {
|
|
60
|
+
return true
|
|
61
|
+
}
|
|
62
|
+
return false
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Reads all resources for Sqlite deployment - see '../../../../lib.deploy'
|
|
68
|
+
*
|
|
69
|
+
* @param {object} model
|
|
70
|
+
* @returns
|
|
71
|
+
*/
|
|
72
|
+
async _getSqliteResources(model) {
|
|
73
|
+
const { resources } = this.handler.cds.deploy
|
|
74
|
+
return Object.keys(await resources(model))
|
|
50
75
|
}
|
|
51
76
|
|
|
52
77
|
/**
|
|
@@ -56,10 +81,10 @@ class ResourcesTarBuilder {
|
|
|
56
81
|
* @returns {string} the src folder of the tenant db module
|
|
57
82
|
*/
|
|
58
83
|
async _getHanaTenantDbDest() {
|
|
59
|
-
const buildOptions = this.
|
|
84
|
+
const buildOptions = this.handler.buildOptions
|
|
60
85
|
let hanaTask = buildOptions.tasks ? buildOptions.tasks.find(task => task.for === BUILD_TASK_HANA) : undefined
|
|
61
86
|
if (!hanaTask) {
|
|
62
|
-
this.
|
|
87
|
+
this.handler.pushMessage(`Found SQLite database configuration. Ensure that productive cloud deployments requiring SAP HANA database are built with 'production' profile.`, WARNING)
|
|
63
88
|
return null
|
|
64
89
|
}
|
|
65
90
|
return hanaTask.dest
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const BuildTaskHandlerInternal = require('../buildTaskHandlerInternal')
|
|
3
|
+
const { FOLDER_GEN } = require('../../constants')
|
|
4
|
+
const ResourcesTarBuilder = require('../mtx/resourcesTarBuilder')
|
|
5
|
+
|
|
6
|
+
class MtxExtensionModuleBuilder extends BuildTaskHandlerInternal {
|
|
7
|
+
init() {
|
|
8
|
+
super.init()
|
|
9
|
+
if (this.buildOptions.root === this.buildOptions.target) {
|
|
10
|
+
this.task.dest = path.join(this.task.dest, FOLDER_GEN)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async build() {
|
|
15
|
+
const model = await this.model()
|
|
16
|
+
if (!model) {
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
const allFiles = []
|
|
20
|
+
const destExt = path.join(this.task.dest, 'ext')
|
|
21
|
+
|
|
22
|
+
const packageJson = path.join(destExt, 'package.json')
|
|
23
|
+
await this.copy(path.join(this.task.src, 'package.json')).to(packageJson)
|
|
24
|
+
allFiles.push(packageJson)
|
|
25
|
+
|
|
26
|
+
// extension CSN using parsed format
|
|
27
|
+
const options = { ...this.options(), flavor: 'parsed' }
|
|
28
|
+
const extCsn = await this.cds.load(this.resolveModel(), options)
|
|
29
|
+
if (extCsn.requires) {
|
|
30
|
+
extCsn.requires.length = 0
|
|
31
|
+
}
|
|
32
|
+
const csnFile = path.join(destExt, 'extension.csn')
|
|
33
|
+
await this.compileToJson(extCsn, csnFile)
|
|
34
|
+
allFiles.push(csnFile)
|
|
35
|
+
|
|
36
|
+
const i18n = await this.collectLanguageBundles(extCsn, destExt)
|
|
37
|
+
if (i18n) {
|
|
38
|
+
allFiles.push(i18n.file)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const files = Object.keys(await this.cds.deploy.resources(model))
|
|
42
|
+
if (files.length > 0) {
|
|
43
|
+
const dataDest = path.join(destExt, 'data')
|
|
44
|
+
await Promise.all(
|
|
45
|
+
files
|
|
46
|
+
.filter(file => /\.csv$/.test(file))
|
|
47
|
+
.map(csv => {
|
|
48
|
+
const csvFile = path.join(dataDest, path.basename(csv))
|
|
49
|
+
allFiles.push(csvFile)
|
|
50
|
+
return this.copy(csv).to(csvFile)
|
|
51
|
+
})
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
await new ResourcesTarBuilder(this).writeTarFile(allFiles, destExt, path.join(this.task.dest, 'extension.tgz'))
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
module.exports = MtxExtensionModuleBuilder
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
/* eslint-disable no-empty */
|
|
2
1
|
const path = require('path')
|
|
3
|
-
const
|
|
4
|
-
const
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const { FOLDER_GEN, DEFAULT_CSN_FILE_NAME } = require('../../constants')
|
|
5
4
|
const NodeCfModuleBuilder = require('../nodejs')
|
|
6
5
|
const ResourcesTarProvider = require('../mtx/resourcesTarBuilder')
|
|
7
|
-
const { INFO } =
|
|
8
|
-
const { relativePaths } = require('
|
|
6
|
+
const { INFO, ERROR } = NodeCfModuleBuilder
|
|
7
|
+
const { relativePaths, BuildError } = require('../../util')
|
|
9
8
|
|
|
10
9
|
const DEFAULT_MAIN_FOLDER = "_main"
|
|
11
10
|
|
|
12
11
|
class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
|
|
13
12
|
get priority() {
|
|
14
13
|
// should be scheduled after 'hana' build tasks are finished
|
|
15
|
-
return
|
|
14
|
+
return NodeCfModuleBuilder.PRIORITY_MIN_VALUE
|
|
16
15
|
}
|
|
17
16
|
init() {
|
|
18
17
|
super.init()
|
|
@@ -25,7 +24,7 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
|
|
|
25
24
|
* Builds the mtx sidecar app consisting of:
|
|
26
25
|
* - nodejs app model defined by the required sidecar services
|
|
27
26
|
* - main app model defined by the build task's model options including feature models and resources TAR
|
|
28
|
-
*
|
|
27
|
+
*
|
|
29
28
|
* build.target=".": 'dest' -> 'model-provider/gen'
|
|
30
29
|
* build.target="gen": 'dest' -> 'gen/model-provider'
|
|
31
30
|
*/
|
|
@@ -46,7 +45,7 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
|
|
|
46
45
|
const destSidecar = this.task.dest
|
|
47
46
|
const destSidecarSrc = path.join(destSidecar, this.env.folders.srv)
|
|
48
47
|
const model = this._compileSidecarSync(sidecarEnv)
|
|
49
|
-
await this.compileToJson(model, destSidecarSrc)
|
|
48
|
+
await this.compileToJson(model, path.join(destSidecarSrc, DEFAULT_CSN_FILE_NAME))
|
|
50
49
|
await this.collectLanguageBundles(model, destSidecarSrc)
|
|
51
50
|
await this._copyProjectRootContent(this.task.src, destSidecar)
|
|
52
51
|
}
|
|
@@ -56,11 +55,12 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
|
|
|
56
55
|
* @param {object} sidecarEnv cds env based on the sidecar dir
|
|
57
56
|
*/
|
|
58
57
|
async _buildMainApp(sidecarEnv) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
throw new Error("Invalid sidecar configuration - {\"model-provider\": \"in-sidecar\"} configuration missing")
|
|
58
|
+
if (sidecarEnv.requires['cds.xt.ModelProviderService']?.kind !== 'in-sidecar') {
|
|
59
|
+
throw new Error("Invalid MTX sidecar configuration - \"cds.xt.ModelProviderService\": \"in-sidecar\" missing")
|
|
62
60
|
}
|
|
61
|
+
let main = sidecarEnv.requires['cds.xt.ModelProviderService']?.root
|
|
63
62
|
const profiles = this.env.get("profiles") || []
|
|
63
|
+
|
|
64
64
|
if (!profiles.includes("production") && !profiles.includes("prod")) {
|
|
65
65
|
main = DEFAULT_MAIN_FOLDER
|
|
66
66
|
// root should represent the production use case and not development
|
|
@@ -78,7 +78,10 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
|
|
|
78
78
|
|
|
79
79
|
// create resources TAR
|
|
80
80
|
// resources are determined based on available database build task, SQLite as fallback
|
|
81
|
-
await new ResourcesTarProvider(this).
|
|
81
|
+
await new ResourcesTarProvider(this).createTar(destMain, csn)
|
|
82
|
+
|
|
83
|
+
// copy package.json and .cdsrc.json from project root
|
|
84
|
+
await this._copyMainConfigFiles(this.cds.root, destMain)
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
/**
|
|
@@ -91,20 +94,45 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
|
|
|
91
94
|
try {
|
|
92
95
|
this.cds.root = this.task.src
|
|
93
96
|
this.cds.env = sidecarEnv
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
const modelPaths = this.cds.resolve('*', false)
|
|
98
|
+
const modelFilePaths = this.cds.resolve(modelPaths)
|
|
99
|
+
|
|
100
|
+
if (!modelFilePaths || modelFilePaths.length === 0) {
|
|
101
|
+
throw new BuildError("No model found for MTX sidecar app")
|
|
98
102
|
}
|
|
99
|
-
|
|
103
|
+
// candidate for strict mode support
|
|
104
|
+
// entries for @sap/** model paths are missing if the module hasn't been installed, e.g. @sap/cds-mtxs
|
|
105
|
+
const missingModels = modelPaths.filter(p => {
|
|
106
|
+
if (p.startsWith('@sap/')) {
|
|
107
|
+
const files = this.cds.resolve(p)
|
|
108
|
+
return !files || files.length === 0
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
if (missingModels.length > 0) {
|
|
112
|
+
this.pushMessage(`Some model paths could not be resolved. Make sure the npm modules have been installed: ${missingModels.join(', ')}`, ERROR)
|
|
113
|
+
}
|
|
114
|
+
this._logger._debug && this._logger.debug(`sidecar model: ${relativePaths(this.buildOptions.root, modelFilePaths).join(", ")}`)
|
|
100
115
|
|
|
101
116
|
// synchronous compilation
|
|
102
|
-
return this.cds.load(
|
|
117
|
+
return this.cds.load(modelFilePaths, { sync: true, ...this.options() })
|
|
103
118
|
} finally {
|
|
104
119
|
// restore project scope
|
|
105
120
|
this.cds.root = this.buildOptions.root
|
|
106
121
|
this.cds.env = env
|
|
107
122
|
}
|
|
108
123
|
}
|
|
124
|
+
|
|
125
|
+
async _copyMainConfigFiles(src, dest) {
|
|
126
|
+
const packageJson = path.join(src, 'package.json')
|
|
127
|
+
const cdsrcJson = path.join(src, '.cdsrc.json')
|
|
128
|
+
const promises = []
|
|
129
|
+
if (fs.existsSync(packageJson)) {
|
|
130
|
+
promises.push(this.copy(packageJson).to(path.join(dest, 'package.json')))
|
|
131
|
+
}
|
|
132
|
+
if (fs.existsSync(cdsrcJson)) {
|
|
133
|
+
promises.push(this.copy(cdsrcJson).to(path.join(dest, '.cdsrc.json')))
|
|
134
|
+
}
|
|
135
|
+
return promises
|
|
136
|
+
}
|
|
109
137
|
}
|
|
110
138
|
module.exports = MtxSidecarModuleBuilder
|