@sap/cds 6.4.1 → 6.6.0
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 +79 -6
- package/README.md +5 -0
- package/apis/cqn.d.ts +14 -3
- package/apis/ql.d.ts +8 -8
- package/apis/services.d.ts +37 -65
- package/apis/test.d.ts +7 -0
- package/bin/build/buildTaskEngine.js +9 -14
- package/bin/build/buildTaskFactory.js +1 -1
- package/bin/build/buildTaskHandler.js +3 -14
- package/bin/build/index.js +8 -2
- package/bin/build/provider/buildTaskProviderInternal.js +18 -13
- package/bin/build/provider/fiori/index.js +5 -10
- package/bin/build/provider/hana/2migration.js +11 -2
- package/bin/build/provider/hana/index.js +17 -14
- package/bin/build/provider/hana/template/.hdiconfig-hanacloud +137 -0
- package/bin/build/provider/hana/template/package.json +3 -0
- package/bin/build/provider/mtx/resourcesTarBuilder.js +12 -3
- package/bin/build/provider/mtx-extension/index.js +57 -37
- package/bin/build/provider/mtx-sidecar/index.js +1 -1
- package/bin/build/util.js +18 -1
- package/bin/cds.js +1 -5
- package/bin/deploy/to-hana/hana.js +10 -3
- package/bin/serve.js +36 -20
- package/common.cds +7 -0
- package/lib/auth/jwt-auth.js +8 -7
- package/lib/compile/for/lean_drafts.js +55 -6
- package/lib/compile/minify.js +3 -3
- package/lib/dbs/cds-deploy.js +18 -17
- package/lib/env/cds-requires.js +1 -1
- package/lib/env/defaults.js +5 -1
- package/lib/env/schemas/cds-rc.json +74 -3
- package/lib/index.js +4 -2
- package/lib/lazy.js +6 -8
- package/lib/log/cds-error.js +2 -2
- package/lib/ql/Whereable.js +22 -11
- package/lib/ql/cds-ql.js +1 -1
- package/lib/req/cds-context.js +3 -3
- package/lib/req/response.js +8 -3
- package/lib/req/user.js +12 -2
- package/lib/srv/bindings.js +1 -2
- package/lib/srv/cds-serve.js +2 -1
- package/lib/srv/middlewares/trace.js +31 -15
- package/lib/srv/protocols/odata-v2-proxy.js +8 -8
- package/lib/srv/srv-handlers.js +26 -7
- package/lib/srv/srv-methods.js +2 -2
- package/lib/srv/srv-models.js +8 -3
- package/lib/utils/cds-test.js +7 -5
- package/lib/utils/cds-utils.js +3 -1
- package/lib/utils/tar.js +6 -3
- package/libx/_runtime/auth/strategies/JWT.js +1 -0
- package/libx/_runtime/auth/strategies/ias-auth.js +3 -2
- package/libx/_runtime/auth/strategies/mock.js +12 -1
- package/libx/_runtime/auth/strategies/xssecUtils.js +7 -8
- package/libx/_runtime/auth/strategies/xsuaa.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +8 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +11 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +8 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/validator/ValueValidator.js +14 -14
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ResourceJsonSerializer.js +3 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/UriHelper.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +3 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +7 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +0 -3
- package/libx/_runtime/cds-services/services/Service.js +11 -19
- package/libx/_runtime/cds-services/services/utils/columns.js +42 -40
- package/libx/_runtime/cds-services/util/assert.js +7 -1
- package/libx/_runtime/common/code-ext/WorkerReq.js +81 -0
- package/libx/_runtime/common/code-ext/config.js +13 -0
- package/libx/_runtime/common/code-ext/execute.js +113 -0
- package/libx/_runtime/common/code-ext/handlers.js +49 -0
- package/libx/_runtime/common/code-ext/worker.js +40 -0
- package/libx/_runtime/common/code-ext/workerQuery.js +45 -0
- package/libx/_runtime/common/code-ext/workerQueryExecutor.js +36 -0
- package/libx/_runtime/common/composition/data.js +5 -2
- package/libx/_runtime/common/composition/tree.js +2 -0
- package/libx/_runtime/common/generic/auth/restrict.js +1 -1
- package/libx/_runtime/common/generic/crud.js +4 -0
- package/libx/_runtime/common/generic/etag.js +3 -1
- package/libx/_runtime/common/generic/input.js +12 -14
- package/libx/_runtime/common/i18n/index.js +1 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +47 -22
- package/libx/_runtime/common/utils/path.js +5 -26
- package/libx/_runtime/common/utils/search2cqn4sql.js +16 -9
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +19 -13
- package/libx/_runtime/db/data-conversion/post-processing.js +1 -1
- package/libx/_runtime/db/expand/expandCQNToJoin.js +7 -4
- package/libx/_runtime/db/expand/rawToExpanded.js +3 -2
- package/libx/_runtime/db/generic/input.js +2 -2
- package/libx/_runtime/db/generic/integrity.js +1 -0
- package/libx/_runtime/db/generic/virtual.js +1 -0
- package/libx/_runtime/db/query/read.js +3 -2
- package/libx/_runtime/db/utils/localized.js +1 -1
- package/libx/_runtime/fiori/generic/activate.js +7 -1
- package/libx/_runtime/fiori/generic/before.js +9 -1
- package/libx/_runtime/fiori/generic/edit.js +8 -1
- package/libx/_runtime/fiori/generic/new.js +2 -0
- package/libx/_runtime/fiori/generic/patch.js +2 -0
- package/libx/_runtime/fiori/generic/prepare.js +2 -0
- package/libx/_runtime/fiori/generic/read.js +16 -5
- package/libx/_runtime/fiori/generic/readOverDraft.js +2 -0
- package/libx/_runtime/fiori/lean-draft.js +505 -241
- package/libx/_runtime/fiori/utils/delete.js +2 -0
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -5
- package/libx/_runtime/hana/pool.js +1 -1
- package/libx/_runtime/hana/search2cqn4sql.js +51 -51
- package/libx/_runtime/messaging/Outbox.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -0
- package/libx/_runtime/messaging/enterprise-messaging.js +2 -6
- package/libx/_runtime/messaging/file-based.js +1 -2
- package/libx/_runtime/messaging/outbox/OutboxRunner.js +1 -1
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/messaging/service.js +0 -1
- package/libx/_runtime/remote/Service.js +1 -0
- package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +19 -3
- package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +0 -18
- package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +0 -18
- package/libx/_runtime/sqlite/customBuilder/CustomSelectBuilder.js +0 -24
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +2 -1
- package/libx/_runtime/sqlite/customBuilder/index.js +47 -32
- package/libx/odata/afterburner.js +23 -8
- package/libx/odata/cqn2odata.js +1 -1
- package/libx/odata/grammar.pegjs +3 -4
- package/libx/odata/index.js +5 -1
- package/libx/odata/parseToCqn.js +3 -3
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +58 -1
- package/libx/rest/middleware/parse.js +26 -4
- package/package.json +1 -1
- package/server.js +1 -1
- package/libx/_runtime/sqlite/customBuilder/CustomDeleteBuilder.js +0 -17
- package/libx/_runtime/sqlite/customBuilder/CustomReferenceBuilder.js +0 -11
- package/libx/_runtime/sqlite/customBuilder/CustomUpdateBuilder.js +0 -17
- /package/bin/build/provider/hana/template/{.hdiconfig → .hdiconfig-haas} +0 -0
|
@@ -62,10 +62,9 @@ class FioriAppModuleBuilder extends BuildTaskHandlerEdmx {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
for (let [appFolder, appModelGroup] of appModelGroups.entries()) {
|
|
65
|
-
this.logger.
|
|
65
|
+
this.logger.debug(`building module [${appFolder}] using [${this.constructor.name}]`)
|
|
66
66
|
const modelPaths = cds.resolve(Array.from(appModelGroup.values()), this.buildOptions)
|
|
67
67
|
if (!modelPaths || modelPaths.length === 0) {
|
|
68
|
-
this.logger.log(`no model found`)
|
|
69
68
|
continue
|
|
70
69
|
}
|
|
71
70
|
this.logger._debug && this.logger.debug(`model: ${relativePaths(this.buildOptions.root, modelPaths).join(", ")}`)
|
|
@@ -89,19 +88,15 @@ class FioriAppModuleBuilder extends BuildTaskHandlerEdmx {
|
|
|
89
88
|
/**
|
|
90
89
|
* This version only creates a odata representation for the 'mainService' data source
|
|
91
90
|
* as defined by the fiori wizard - everything else is currently not supported.
|
|
92
|
-
* Therefore errors are only logged, the build does not fail in case a
|
|
91
|
+
* Therefore errors are only logged, the build does not fail in case a service
|
|
93
92
|
* cannot be resolved based on the defined service URI
|
|
94
93
|
*/
|
|
95
94
|
async build() {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
if (!modelPaths || modelPaths.length === 0) {
|
|
99
|
-
this.logger.log(`no model found`)
|
|
95
|
+
const model = await this.model()
|
|
96
|
+
if (!model) {
|
|
100
97
|
return
|
|
101
98
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
await this._writeEdmxToWebapp(src, dest)
|
|
99
|
+
await this._writeEdmxToWebapp(this.task.src, this.task.dest)
|
|
105
100
|
}
|
|
106
101
|
|
|
107
102
|
async _writeEdmxToWebapp(src, dest) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
2
|
const parser = require('./migrationtable')
|
|
3
|
-
const { BuildError } = require('../../util')
|
|
3
|
+
const { BuildError, hasOptionValue } = require('../../util')
|
|
4
4
|
const { LOG_MODULE_NAMES } = require('../../constants')
|
|
5
5
|
const cds = require('../../cds'), { compiler: cdsc } = cds
|
|
6
6
|
const cdscVersion = `-- generated by cds-compiler version ${cdsc.version()}`
|
|
@@ -160,7 +160,7 @@ function _filterJournalArtifacts(csn) {
|
|
|
160
160
|
}
|
|
161
161
|
const dict = csn.definitions
|
|
162
162
|
for (const name in dict) {
|
|
163
|
-
if (dict[name]
|
|
163
|
+
if (!_isPersistedAsJournalTable(dict[name])) {
|
|
164
164
|
delete dict[name]
|
|
165
165
|
}
|
|
166
166
|
}
|
|
@@ -168,3 +168,12 @@ function _filterJournalArtifacts(csn) {
|
|
|
168
168
|
csn.meta.build = `CDS Build v${cds.version}`
|
|
169
169
|
return csn;
|
|
170
170
|
}
|
|
171
|
+
|
|
172
|
+
// see cds-compiler/lib/model/csnUtils.js#isPersistedAsTable
|
|
173
|
+
function _isPersistedAsJournalTable(artifact) {
|
|
174
|
+
return artifact.kind === 'entity' && hasOptionValue(artifact['@cds.persistence.journal'], true) &&
|
|
175
|
+
!artifact.abstract &&
|
|
176
|
+
!hasOptionValue(artifact['@cds.persistence.skip'], true) &&
|
|
177
|
+
!hasOptionValue(artifact['@cds.persistence.exists'], true) &&
|
|
178
|
+
(!artifact.query && !artifact.projection || hasOptionValue(artifact['@cds.persistence.table'], true))
|
|
179
|
+
}
|
|
@@ -338,6 +338,12 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
|
|
|
338
338
|
if (!HanaModuleBuilder._toEqualIgnoreMeta(lastDev, afterImage)) {
|
|
339
339
|
await this.write(afterImage).to(lastDevCsnDir)
|
|
340
340
|
}
|
|
341
|
+
|
|
342
|
+
// add src/.hdiconfig if not existing
|
|
343
|
+
if (!fs.existsSync(path.join(dbSrcDir, '.hdiconfig'))) {
|
|
344
|
+
const template = await HanaModuleBuilder._readTemplateAsJson('.hdiconfig-hanacloud')
|
|
345
|
+
await this.write(template).to(path.join(dbSrcDir, '.hdiconfig'))
|
|
346
|
+
}
|
|
341
347
|
}
|
|
342
348
|
} else {
|
|
343
349
|
throw new BuildError(`Inconsistent CDS compilation results - file ${lastDevCsnFolder} missing`)
|
|
@@ -352,7 +358,7 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
|
|
|
352
358
|
this.logger._debug && this.logger.debug(`skip create [${relativePaths(this.buildOptions.root, packageJson)}], already existing`)
|
|
353
359
|
}
|
|
354
360
|
if (this.isStagingBuild() && !exists) {
|
|
355
|
-
const content = await
|
|
361
|
+
const content = await HanaModuleBuilder._readTemplateAsJson(FILE_NAME_PACKAGE_JSON)
|
|
356
362
|
await this.write(content).to(path.join(this.task.dest, FILE_NAME_PACKAGE_JSON))
|
|
357
363
|
}
|
|
358
364
|
}
|
|
@@ -362,7 +368,7 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
|
|
|
362
368
|
*/
|
|
363
369
|
async _writeHdiConfig(plugins) {
|
|
364
370
|
const hdiConfig = path.join(this.task.options.compileDest, FILE_NAME_HDICONFIG)
|
|
365
|
-
const template = await
|
|
371
|
+
const template = await HanaModuleBuilder._readTemplateAsJson('.hdiconfig-haas')
|
|
366
372
|
let content = {
|
|
367
373
|
'file_suffixes': {}
|
|
368
374
|
}
|
|
@@ -372,7 +378,7 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
|
|
|
372
378
|
}
|
|
373
379
|
}
|
|
374
380
|
if (Object.keys(content['file_suffixes']).length !== plugins.size) {
|
|
375
|
-
this.
|
|
381
|
+
this.pushMessage(`'HANA plugin not found for file suffix [${Array.from(plugins).join(',')}]`)
|
|
376
382
|
}
|
|
377
383
|
// TODO - Be on the save side for now - go for the content use case later on if this works as expected.
|
|
378
384
|
if (cds.env.hana['deploy-format'] === 'hdbtable') {
|
|
@@ -388,7 +394,7 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
|
|
|
388
394
|
async _writeHdiNamespace() {
|
|
389
395
|
// see issue #64 - add .hdinamespace file to prevent HDI from adding gen/ folder to the namespace.
|
|
390
396
|
const hdiNamespace = path.join(this.task.options.compileDest, FILE_NAME_HDINAMESPACE)
|
|
391
|
-
const content = await
|
|
397
|
+
const content = await HanaModuleBuilder._readTemplateAsJson(FILE_NAME_HDINAMESPACE)
|
|
392
398
|
return await this.write(content).to(hdiNamespace)
|
|
393
399
|
}
|
|
394
400
|
|
|
@@ -400,7 +406,7 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
|
|
|
400
406
|
// see issue #64 - add .hdinamespace file to prevent HDI from adding gen/ folder to the namespace.
|
|
401
407
|
const undeployJsonDest = path.join(this.task.dest, FILE_NAME_UNDEPLOY_JSON)
|
|
402
408
|
const undeployJsonSrc = path.join(this.task.src, FILE_NAME_UNDEPLOY_JSON)
|
|
403
|
-
const templateEntries = await
|
|
409
|
+
const templateEntries = await HanaModuleBuilder._readTemplateAsJson(FILE_NAME_UNDEPLOY_JSON)
|
|
404
410
|
let newEntries = []
|
|
405
411
|
if (fs.existsSync(undeployJsonSrc)) {
|
|
406
412
|
newEntries = await JSON.parse((await fs.promises.readFile(undeployJsonSrc, 'utf-8')).toString())
|
|
@@ -423,20 +429,12 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
|
|
|
423
429
|
}
|
|
424
430
|
}
|
|
425
431
|
|
|
426
|
-
async _readTemplateAsJson(template) {
|
|
427
|
-
const templatePath = path.join(__dirname, 'template', template)
|
|
428
|
-
return fs.promises.readFile(templatePath, 'utf-8').then(f => JSON.parse(f.toString()))
|
|
429
|
-
.catch((error) => {
|
|
430
|
-
this.logger.error(`Failed to read template [${templatePath}]`)
|
|
431
|
-
return Promise.reject(error)
|
|
432
|
-
})
|
|
433
|
-
}
|
|
434
432
|
|
|
435
433
|
async _readTypesFromUndeployJson() {
|
|
436
434
|
const result = new Set()
|
|
437
435
|
const file = path.join(this.task.src, "undeploy.json")
|
|
438
436
|
if (fs.existsSync(file)) {
|
|
439
|
-
const undeployList = JSON.parse((await fs.promises.readFile(file)).toString())
|
|
437
|
+
const undeployList = JSON.parse((await fs.promises.readFile(file)).toString(), 'utf-8')
|
|
440
438
|
if (Array.isArray(undeployList)) {
|
|
441
439
|
undeployList.forEach(entry => result.add(path.extname(entry)))
|
|
442
440
|
}
|
|
@@ -466,6 +464,11 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
|
|
|
466
464
|
}
|
|
467
465
|
}
|
|
468
466
|
|
|
467
|
+
static async _readTemplateAsJson(template) {
|
|
468
|
+
const content = await fs.promises.readFile(path.join(__dirname, 'template', template), 'utf-8')
|
|
469
|
+
return JSON.parse(content.toString())
|
|
470
|
+
}
|
|
471
|
+
|
|
469
472
|
static _toEqualIgnoreMeta(csn1, csn2) {
|
|
470
473
|
function toString(csn) {
|
|
471
474
|
return JSON.stringify(csn, (k, v) => {
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
|
|
2
|
+
{
|
|
3
|
+
"file_suffixes": {
|
|
4
|
+
"csv": {
|
|
5
|
+
"plugin_name": "com.sap.hana.di.tabledata.source"
|
|
6
|
+
},
|
|
7
|
+
"hdbafllangprocedure": {
|
|
8
|
+
"plugin_name": "com.sap.hana.di.afllangprocedure"
|
|
9
|
+
},
|
|
10
|
+
"hdbanalyticprivilege": {
|
|
11
|
+
"plugin_name": "com.sap.hana.di.analyticprivilege"
|
|
12
|
+
},
|
|
13
|
+
"hdbcalculationview": {
|
|
14
|
+
"plugin_name": "com.sap.hana.di.calculationview"
|
|
15
|
+
},
|
|
16
|
+
"hdbcollection": {
|
|
17
|
+
"plugin_name": "com.sap.hana.di.collection"
|
|
18
|
+
},
|
|
19
|
+
"hdbconstraint": {
|
|
20
|
+
"plugin_name": "com.sap.hana.di.constraint"
|
|
21
|
+
},
|
|
22
|
+
"hdbdropcreatetable": {
|
|
23
|
+
"plugin_name": "com.sap.hana.di.dropcreatetable"
|
|
24
|
+
},
|
|
25
|
+
"hdbflowgraph": {
|
|
26
|
+
"plugin_name": "com.sap.hana.di.flowgraph"
|
|
27
|
+
},
|
|
28
|
+
"hdbfunction": {
|
|
29
|
+
"plugin_name": "com.sap.hana.di.function"
|
|
30
|
+
},
|
|
31
|
+
"hdbgraphworkspace": {
|
|
32
|
+
"plugin_name": "com.sap.hana.di.graphworkspace"
|
|
33
|
+
},
|
|
34
|
+
"hdbhadoopmrjob": {
|
|
35
|
+
"plugin_name": "com.sap.hana.di.virtualfunctionpackage.hadoop"
|
|
36
|
+
},
|
|
37
|
+
"hdbindex": {
|
|
38
|
+
"plugin_name": "com.sap.hana.di.index"
|
|
39
|
+
},
|
|
40
|
+
"hdblibrary": {
|
|
41
|
+
"plugin_name": "com.sap.hana.di.library"
|
|
42
|
+
},
|
|
43
|
+
"hdbmigrationtable": {
|
|
44
|
+
"plugin_name": "com.sap.hana.di.table.migration"
|
|
45
|
+
},
|
|
46
|
+
"hdbprocedure": {
|
|
47
|
+
"plugin_name": "com.sap.hana.di.procedure"
|
|
48
|
+
},
|
|
49
|
+
"hdbprojectionview": {
|
|
50
|
+
"plugin_name": "com.sap.hana.di.projectionview"
|
|
51
|
+
},
|
|
52
|
+
"hdbprojectionviewconfig": {
|
|
53
|
+
"plugin_name": "com.sap.hana.di.projectionview.config"
|
|
54
|
+
},
|
|
55
|
+
"hdbreptask": {
|
|
56
|
+
"plugin_name": "com.sap.hana.di.reptask"
|
|
57
|
+
},
|
|
58
|
+
"hdbresultcache": {
|
|
59
|
+
"plugin_name": "com.sap.hana.di.resultcache"
|
|
60
|
+
},
|
|
61
|
+
"hdbrole": {
|
|
62
|
+
"plugin_name": "com.sap.hana.di.role"
|
|
63
|
+
},
|
|
64
|
+
"hdbroleconfig": {
|
|
65
|
+
"plugin_name": "com.sap.hana.di.role.config"
|
|
66
|
+
},
|
|
67
|
+
"hdbsearchruleset": {
|
|
68
|
+
"plugin_name": "com.sap.hana.di.searchruleset"
|
|
69
|
+
},
|
|
70
|
+
"hdbsequence": {
|
|
71
|
+
"plugin_name": "com.sap.hana.di.sequence"
|
|
72
|
+
},
|
|
73
|
+
"hdbstatistics": {
|
|
74
|
+
"plugin_name": "com.sap.hana.di.statistics"
|
|
75
|
+
},
|
|
76
|
+
"hdbstructuredprivilege": {
|
|
77
|
+
"plugin_name": "com.sap.hana.di.structuredprivilege"
|
|
78
|
+
},
|
|
79
|
+
"hdbsynonym": {
|
|
80
|
+
"plugin_name": "com.sap.hana.di.synonym"
|
|
81
|
+
},
|
|
82
|
+
"hdbsynonymconfig": {
|
|
83
|
+
"plugin_name": "com.sap.hana.di.synonym.config"
|
|
84
|
+
},
|
|
85
|
+
"hdbsystemversioning": {
|
|
86
|
+
"plugin_name": "com.sap.hana.di.systemversioning"
|
|
87
|
+
},
|
|
88
|
+
"hdbtable": {
|
|
89
|
+
"plugin_name": "com.sap.hana.di.table"
|
|
90
|
+
},
|
|
91
|
+
"hdbtabledata": {
|
|
92
|
+
"plugin_name": "com.sap.hana.di.tabledata"
|
|
93
|
+
},
|
|
94
|
+
"hdbtabletype": {
|
|
95
|
+
"plugin_name": "com.sap.hana.di.tabletype"
|
|
96
|
+
},
|
|
97
|
+
"hdbtrigger": {
|
|
98
|
+
"plugin_name": "com.sap.hana.di.trigger"
|
|
99
|
+
},
|
|
100
|
+
"hdbview": {
|
|
101
|
+
"plugin_name": "com.sap.hana.di.view"
|
|
102
|
+
},
|
|
103
|
+
"hdbvirtualfunction": {
|
|
104
|
+
"plugin_name": "com.sap.hana.di.virtualfunction"
|
|
105
|
+
},
|
|
106
|
+
"hdbvirtualfunctionconfig": {
|
|
107
|
+
"plugin_name": "com.sap.hana.di.virtualfunction.config"
|
|
108
|
+
},
|
|
109
|
+
"hdbvirtualpackagehadoop": {
|
|
110
|
+
"plugin_name": "com.sap.hana.di.virtualpackage.hadoop"
|
|
111
|
+
},
|
|
112
|
+
"hdbvirtualpackagesparksql": {
|
|
113
|
+
"plugin_name": "com.sap.hana.di.virtualpackage.sparksql"
|
|
114
|
+
},
|
|
115
|
+
"hdbvirtualprocedure": {
|
|
116
|
+
"plugin_name": "com.sap.hana.di.virtualprocedure"
|
|
117
|
+
},
|
|
118
|
+
"hdbvirtualprocedureconfig": {
|
|
119
|
+
"plugin_name": "com.sap.hana.di.virtualprocedure.config"
|
|
120
|
+
},
|
|
121
|
+
"hdbvirtualtable": {
|
|
122
|
+
"plugin_name": "com.sap.hana.di.virtualtable"
|
|
123
|
+
},
|
|
124
|
+
"hdbvirtualtableconfig": {
|
|
125
|
+
"plugin_name": "com.sap.hana.di.virtualtable.config"
|
|
126
|
+
},
|
|
127
|
+
"properties": {
|
|
128
|
+
"plugin_name": "com.sap.hana.di.tabledata.properties"
|
|
129
|
+
},
|
|
130
|
+
"tags": {
|
|
131
|
+
"plugin_name": "com.sap.hana.di.tabledata.properties"
|
|
132
|
+
},
|
|
133
|
+
"txt": {
|
|
134
|
+
"plugin_name": "com.sap.hana.di.copyonly"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -21,12 +21,21 @@ class ResourcesTarBuilder {
|
|
|
21
21
|
this.handler.pushMessage("No deployment resources found - skip resources.tgz", WARNING)
|
|
22
22
|
return
|
|
23
23
|
}
|
|
24
|
-
await this.writeTarFile(
|
|
24
|
+
await this.writeTarFile(path.join(dest, DEFAULT_TAR_NAME), root, resources)
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Creates a TAR file at the given absolute tarPath. An optional resources list holds the files and folders
|
|
29
|
+
* that will be added to the TAR. Paths are relative to the passed root directory. If omitted, the entire
|
|
30
|
+
* root directory contents will be added.
|
|
31
|
+
* @param {*} tarFile Absolute TAR file name.
|
|
32
|
+
* @param {*} root The root directory the passed resources are relative to.
|
|
33
|
+
* @param {*} resources Optional list of absolute or relative resource paths - relative to the given root directory.
|
|
34
|
+
* If omitted all resources contained in the root directory are added to the TAR file.
|
|
35
|
+
*/
|
|
36
|
+
async writeTarFile(tarFile, root, resources) {
|
|
28
37
|
const { tar } = require('../../../../lib').utils
|
|
29
|
-
await tar.czfd(tarFile, root, resources)
|
|
38
|
+
await tar.czfd(tarFile, root, resources)
|
|
30
39
|
this.handler.pushFile(tarFile)
|
|
31
40
|
}
|
|
32
41
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
|
+
const fs = require('fs')
|
|
2
3
|
const cds = require('../../cds')
|
|
4
|
+
|
|
3
5
|
const BuildTaskHandlerInternal = require('../buildTaskHandlerInternal')
|
|
4
6
|
const { FOLDER_GEN } = require('../../constants')
|
|
5
7
|
const ResourcesTarBuilder = require('../mtx/resourcesTarBuilder')
|
|
8
|
+
const { BuildError } = require('../../util')
|
|
6
9
|
|
|
7
10
|
class MtxExtensionModuleBuilder extends BuildTaskHandlerInternal {
|
|
8
11
|
init() {
|
|
@@ -13,47 +16,64 @@ class MtxExtensionModuleBuilder extends BuildTaskHandlerInternal {
|
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
async build() {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const packageJson = path.join(destExt, 'package.json')
|
|
24
|
-
await this.copy(path.join(this.task.src, 'package.json')).to(packageJson)
|
|
25
|
-
allFiles.push(packageJson)
|
|
26
|
-
|
|
27
|
-
// extension CSN using parsed format
|
|
28
|
-
const options = { ...this.options(), flavor: 'parsed' }
|
|
29
|
-
const extCsn = await cds.load(this.resolveModel(), options)
|
|
30
|
-
if (extCsn.requires) {
|
|
31
|
-
extCsn.requires.length = 0
|
|
19
|
+
const { src, dest } = this.task
|
|
20
|
+
const destExt = path.join(dest, 'ext')
|
|
21
|
+
|
|
22
|
+
const packageJson = path.join(src, 'package.json')
|
|
23
|
+
if (!fs.existsSync(packageJson)) {
|
|
24
|
+
throw new BuildError(`The package.json file is missing`)
|
|
32
25
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
//
|
|
38
|
-
const
|
|
39
|
-
if (
|
|
40
|
-
|
|
26
|
+
await this.copy(packageJson).to(path.join(destExt, 'package.json'))
|
|
27
|
+
|
|
28
|
+
// validate existence of base model by simply checking existence of appPackage folder
|
|
29
|
+
// cds.resolve might fail for the extension migration use case as no index.csn file exists.
|
|
30
|
+
// A compilation error is thrown anyhow if any base model using statement cannot be resolved.
|
|
31
|
+
const appPackage = MtxExtensionModuleBuilder._getAppPackageName()
|
|
32
|
+
if (!fs.existsSync(path.join(src, 'node_modules', appPackage))) {
|
|
33
|
+
throw new BuildError(`The SaaS application base model '${appPackage}' is missing. Have you run the 'cds pull' command?`)
|
|
41
34
|
}
|
|
42
35
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
36
|
+
// copy handlers
|
|
37
|
+
const folders = [path.join(src, cds.env.folders.srv, 'handlers')]
|
|
38
|
+
await this.copyNativeContent(src, destExt, res => {
|
|
39
|
+
if (fs.statSync(res).isDirectory()) {
|
|
40
|
+
return folders.some(folder => folder.startsWith(res))
|
|
41
|
+
}
|
|
42
|
+
if (folders.includes(path.dirname(res)) && /\.js$/.test(res)) {
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const model = await this.model()
|
|
48
|
+
if (model) {
|
|
49
|
+
// extension CSN using parsed format
|
|
50
|
+
const options = { ...this.options(), flavor: 'parsed' }
|
|
51
|
+
const extCsn = await cds.load(this.resolveModel(), options)
|
|
52
|
+
if (extCsn.requires) {
|
|
53
|
+
extCsn.requires.length = 0
|
|
54
|
+
}
|
|
55
|
+
await this.compileToJson(extCsn, path.join(destExt, 'extension.csn'))
|
|
56
|
+
|
|
57
|
+
await this.collectLanguageBundles(extCsn, path.join(destExt, 'i18n'))
|
|
58
|
+
|
|
59
|
+
const files = Object.keys(await cds.deploy.resources(model))
|
|
60
|
+
if (files.length > 0) {
|
|
61
|
+
const dataDest = path.join(destExt, 'data')
|
|
62
|
+
await Promise.all(
|
|
63
|
+
files
|
|
64
|
+
.filter(file => /\.csv$/.test(file))
|
|
65
|
+
.map(csv => {
|
|
66
|
+
return this.copy(csv).to(path.join(dataDest, path.basename(csv)))
|
|
67
|
+
})
|
|
68
|
+
)
|
|
69
|
+
}
|
|
55
70
|
}
|
|
56
|
-
|
|
71
|
+
// add all resources contained in the 'ext' folder
|
|
72
|
+
await new ResourcesTarBuilder(this).writeTarFile(path.join(this.task.dest, 'extension.tgz'), destExt)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static _getAppPackageName() {
|
|
76
|
+
return cds.env.extends || '_base';
|
|
57
77
|
}
|
|
58
78
|
}
|
|
59
79
|
module.exports = MtxExtensionModuleBuilder
|
|
@@ -59,7 +59,7 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
|
|
|
59
59
|
*/
|
|
60
60
|
async _buildMainApp(sidecarEnv) {
|
|
61
61
|
if (sidecarEnv.requires['cds.xt.ModelProviderService']?.kind !== 'in-sidecar') {
|
|
62
|
-
throw new BuildError(
|
|
62
|
+
throw new BuildError('Invalid MTX sidecar configuration - "cds.xt.ModelProviderService": "in-sidecar" missing.')
|
|
63
63
|
}
|
|
64
64
|
let main = sidecarEnv.requires['cds.xt.ModelProviderService']?.root
|
|
65
65
|
const profiles = cds.env.profiles || []
|
package/bin/build/util.js
CHANGED
|
@@ -187,6 +187,22 @@ function flatten(modelPaths) {
|
|
|
187
187
|
}, [])
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Copy a file or directory. The directory can have contents.
|
|
192
|
+
* REVISIT: 'fs.promises.cp' replacement for nodejs 14
|
|
193
|
+
* @param src <String> Note that if src is a directory it will copy everything inside of this directory, not the entire directory itself.
|
|
194
|
+
* @param dest <String> Note that if src is a file, dest cannot be a directory.
|
|
195
|
+
*/
|
|
196
|
+
async function copy(src, dest) {
|
|
197
|
+
if ((await fs.promises.stat(src)).isDirectory()) {
|
|
198
|
+
const entries = await fs.promises.readdir(src)
|
|
199
|
+
return Promise.all(entries.map(async each => copy(path.join(src, each), path.join(dest, each))))
|
|
200
|
+
} else {
|
|
201
|
+
await fs.promises.mkdir(path.dirname(dest), { recursive: true })
|
|
202
|
+
return fs.promises.copyFile(src, dest)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
190
206
|
class BuildMessage extends Error {
|
|
191
207
|
constructor(message, severity = SEVERITY_ERROR) {
|
|
192
208
|
super(message)
|
|
@@ -212,7 +228,7 @@ class BuildError extends BuildMessage {
|
|
|
212
228
|
}
|
|
213
229
|
|
|
214
230
|
toString() {
|
|
215
|
-
return this.message + '\n' + this.messages.map(m => m.toString()).join('\n')
|
|
231
|
+
return this.message + (this.messages.length > 0 ? '\n' + this.messages.map(m => m.toString()).join('\n') : '')
|
|
216
232
|
}
|
|
217
233
|
}
|
|
218
234
|
|
|
@@ -228,6 +244,7 @@ module.exports = {
|
|
|
228
244
|
resolveRequiredSapModels,
|
|
229
245
|
getDefaultModelOptions,
|
|
230
246
|
flatten,
|
|
247
|
+
copy,
|
|
231
248
|
BuildMessage,
|
|
232
249
|
BuildError
|
|
233
250
|
}
|
package/bin/cds.js
CHANGED
|
@@ -9,7 +9,6 @@ const cli = { //NOSONAR
|
|
|
9
9
|
|
|
10
10
|
exec (cmd = process.argv[2], ...argv) {
|
|
11
11
|
if (!argv.length) argv = process.argv.slice(3)
|
|
12
|
-
if (process.env.NODE_ENV !== 'test') this.errorHandlers()
|
|
13
12
|
if (cmd in this.Shortcuts) cmd = process.argv[2] = this.Shortcuts[cmd]
|
|
14
13
|
let task = this.load ('./'+cmd)
|
|
15
14
|
if (task && cmd !== 'build') return task.apply (this, this.args(task,argv))
|
|
@@ -59,10 +58,7 @@ const cli = { //NOSONAR
|
|
|
59
58
|
},
|
|
60
59
|
|
|
61
60
|
errorHandlers () {
|
|
62
|
-
|
|
63
|
-
const _exit = (c) => { console.log(); process.exit(c) }
|
|
64
|
-
cds.repl || process.on ('unhandledRejection', _error)
|
|
65
|
-
cds.repl || process.on ('uncaughtException', _error)
|
|
61
|
+
// for compatibility with lkg -> remove after next release of cds-dk
|
|
66
62
|
},
|
|
67
63
|
|
|
68
64
|
get log() { return this.log = require('./utils/log') }
|
|
@@ -71,8 +71,8 @@ class HanaDeployer {
|
|
|
71
71
|
await fs.write(path.join(currentModelFolder, 'undeploy.json'), JSON.stringify(undeployWhitelist, null, 2));
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
74
|
+
const hasVCAPEnv = Object.keys(vcapEnv).length > 0;
|
|
75
|
+
if (hasVCAPEnv) {
|
|
76
76
|
await fs.mkdir(currentModelFolder, { recursive: true });
|
|
77
77
|
} else {
|
|
78
78
|
const { cfServiceInstanceName, cfServiceInstanceKeyName, serviceKey } =
|
|
@@ -106,7 +106,14 @@ class HanaDeployer {
|
|
|
106
106
|
|
|
107
107
|
await hdiDeployUtil.deploy(currentModelFolder, vcapEnv, hdiOptions);
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
// let isLoggedInToCF;
|
|
110
|
+
// try {
|
|
111
|
+
// isLoggedInToCF = !!(await cfUtil.getCfTarget());
|
|
112
|
+
// } catch (err) {
|
|
113
|
+
// // valid state: not logged in
|
|
114
|
+
// }
|
|
115
|
+
// if (bindCallback && isLoggedInToCF) {
|
|
116
|
+
if (!hasVCAPEnv && bindCallback) {
|
|
110
117
|
const args = [path.relative(projectPath, buildResult.task.src)];
|
|
111
118
|
const options = { to: `${serviceName}:${serviceKeyName}`, kind: buildResult.task.for }
|
|
112
119
|
await bindCallback(args, options);
|
package/bin/serve.js
CHANGED
|
@@ -6,7 +6,7 @@ module.exports = Object.assign ( serve, {
|
|
|
6
6
|
flags: [
|
|
7
7
|
'--project', '--projects',
|
|
8
8
|
'--in-memory', '--in-memory?',
|
|
9
|
-
'--mocked', '--with-mocks', '--with-bindings',
|
|
9
|
+
'--mocked', '--with-mocks', '--with-bindings', '--resolve-bindings',
|
|
10
10
|
'--watch',
|
|
11
11
|
],
|
|
12
12
|
shortcuts: [ '-s', undefined, '-2', '-a', '-w', undefined, '-p' ],
|
|
@@ -98,6 +98,10 @@ module.exports = Object.assign ( serve, {
|
|
|
98
98
|
All required services are bound automatically upon bootstrapping.
|
|
99
99
|
Option *--with-mocks* subsumes this option.
|
|
100
100
|
|
|
101
|
+
*--resolve-bindings* (beta)
|
|
102
|
+
|
|
103
|
+
Resolve remote service bindings configured via *cds bind*.
|
|
104
|
+
|
|
101
105
|
*--in-memory[?]*
|
|
102
106
|
|
|
103
107
|
Automatically adds a transient in-memory database bootstrapped on
|
|
@@ -127,14 +131,14 @@ const cds = require('../lib'), { exists, isfile, local, path } = cds.utils
|
|
|
127
131
|
|
|
128
132
|
// provisional loggers, see _prepare_logging
|
|
129
133
|
let log = console.log
|
|
130
|
-
let debug = false
|
|
131
134
|
|
|
132
135
|
|
|
133
136
|
/**
|
|
134
137
|
* The main function which dispatches into the respective usage variants.
|
|
135
138
|
* @param {string[]} all - project folder, model filenames, or service name
|
|
136
139
|
*/
|
|
137
|
-
async function serve (all=[], o={}) {
|
|
140
|
+
async function serve (all=[], o={}) {
|
|
141
|
+
|
|
138
142
|
// canonicalize options to ease subsequent tasks...
|
|
139
143
|
cds.options = o
|
|
140
144
|
const [pms] = all // project folder, model filenames, or service name
|
|
@@ -196,17 +200,8 @@ async function serve (all=[], o={}) { // NOSONAR
|
|
|
196
200
|
|
|
197
201
|
server.listening ? _started(server) : server.once('listening',_started)
|
|
198
202
|
server.on ('error',_reject) // startup errors like EADDRINUSE
|
|
199
|
-
server.on ('close',
|
|
200
|
-
|
|
201
|
-
process.once('SIGTERM', shutdown)
|
|
202
|
-
process.once('SIGINT', shutdown)
|
|
203
|
-
process.once('SIGHUP', shutdown)
|
|
204
|
-
process.once('SIGUSR2', shutdown) // by nodemon
|
|
205
|
-
process.on('beforeExit', shutdown) //> when event loop empties
|
|
206
|
-
process.on('message', (msg) => { if (msg.close||msg.exit) shutdown() }) // by `cds watch` on Windows
|
|
207
|
-
|
|
208
|
-
return server
|
|
209
|
-
|
|
203
|
+
// server.on ('close', _shutdown) // IMPORTANT: Don't do that as that would be a very strange loop
|
|
204
|
+
// process.on ('exit', _shutdown) // IMPORTANT: Don't do that as that would be a very strange loop
|
|
210
205
|
async function _started() {
|
|
211
206
|
_warn_if_cds_was_loaded_from_different_locations()
|
|
212
207
|
const url = cds.server.url = `http://localhost:${server.address().port}`
|
|
@@ -214,13 +209,34 @@ async function serve (all=[], o={}) { // NOSONAR
|
|
|
214
209
|
_resolve (server)
|
|
215
210
|
}
|
|
216
211
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
212
|
+
cds.shutdown = _shutdown //> for programmatic invocation
|
|
213
|
+
process.on('unhandledRejection', (_,p) => _shutdown (console.error('❗️Uncaught',p)))
|
|
214
|
+
process.on('uncaughtException', (e) => _shutdown (console.error('❗️Uncaught',e)))
|
|
215
|
+
process.on('SIGINT', cds.watched ? _shutdown : (s,n)=>_shutdown(s,n,console.log())) //> newline after ^C
|
|
216
|
+
process.on('SIGHUP', _shutdown)
|
|
217
|
+
process.on('SIGHUP2', _shutdown)
|
|
218
|
+
process.on('SIGTERM', _shutdown)
|
|
219
|
+
|
|
220
|
+
async function _shutdown (signal,n) {
|
|
221
|
+
if (signal) DEBUG?.('⚡️',signal,n, 'received by cds serve')
|
|
222
|
+
await Promise.all(cds.listeners('shutdown').map(fn => fn()))
|
|
223
|
+
server.close(()=>{/* it's ok if closed already */}) // first, we try stopping server and process the nice way
|
|
224
|
+
if (!global.it) setTimeout(process.exit,1111).unref() // after ~1 sec, we force-exit it, unless in test mode
|
|
223
225
|
}
|
|
226
|
+
|
|
227
|
+
const DEBUG = cds.debug('cli')
|
|
228
|
+
if (DEBUG) {
|
|
229
|
+
cds.on('shutdown', () => DEBUG ('⚡️','cds serve - cds.shutdown'))
|
|
230
|
+
server.on('close', () => DEBUG ('⚡️','cds serve - server.close(d)'))
|
|
231
|
+
process.on('exit', () => DEBUG ('⚡️','cds serve - process.exit'))
|
|
232
|
+
process.on('beforeExit', ()=> DEBUG ('⚡️','cds serve - process.beforeExit'))
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (process.platform === 'win32') {
|
|
236
|
+
process.on('message', msg => msg.close && _shutdown()) // by `cds watch` on Windows
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return server
|
|
224
240
|
})
|
|
225
241
|
}
|
|
226
242
|
|
package/common.cds
CHANGED
|
@@ -72,6 +72,13 @@ context sap.common {
|
|
|
72
72
|
name : localized String(255) @title : '{i18n>Name}';
|
|
73
73
|
descr : localized String(1000) @title : '{i18n>Description}';
|
|
74
74
|
}
|
|
75
|
+
|
|
76
|
+
/*
|
|
77
|
+
* Aspect that is included by generated `.texts` entities for localized entities.
|
|
78
|
+
*/
|
|
79
|
+
aspect TextsAspect {
|
|
80
|
+
key locale: Locale;
|
|
81
|
+
}
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
|