@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.
Files changed (139) hide show
  1. package/CHANGELOG.md +79 -6
  2. package/README.md +5 -0
  3. package/apis/cqn.d.ts +14 -3
  4. package/apis/ql.d.ts +8 -8
  5. package/apis/services.d.ts +37 -65
  6. package/apis/test.d.ts +7 -0
  7. package/bin/build/buildTaskEngine.js +9 -14
  8. package/bin/build/buildTaskFactory.js +1 -1
  9. package/bin/build/buildTaskHandler.js +3 -14
  10. package/bin/build/index.js +8 -2
  11. package/bin/build/provider/buildTaskProviderInternal.js +18 -13
  12. package/bin/build/provider/fiori/index.js +5 -10
  13. package/bin/build/provider/hana/2migration.js +11 -2
  14. package/bin/build/provider/hana/index.js +17 -14
  15. package/bin/build/provider/hana/template/.hdiconfig-hanacloud +137 -0
  16. package/bin/build/provider/hana/template/package.json +3 -0
  17. package/bin/build/provider/mtx/resourcesTarBuilder.js +12 -3
  18. package/bin/build/provider/mtx-extension/index.js +57 -37
  19. package/bin/build/provider/mtx-sidecar/index.js +1 -1
  20. package/bin/build/util.js +18 -1
  21. package/bin/cds.js +1 -5
  22. package/bin/deploy/to-hana/hana.js +10 -3
  23. package/bin/serve.js +36 -20
  24. package/common.cds +7 -0
  25. package/lib/auth/jwt-auth.js +8 -7
  26. package/lib/compile/for/lean_drafts.js +55 -6
  27. package/lib/compile/minify.js +3 -3
  28. package/lib/dbs/cds-deploy.js +18 -17
  29. package/lib/env/cds-requires.js +1 -1
  30. package/lib/env/defaults.js +5 -1
  31. package/lib/env/schemas/cds-rc.json +74 -3
  32. package/lib/index.js +4 -2
  33. package/lib/lazy.js +6 -8
  34. package/lib/log/cds-error.js +2 -2
  35. package/lib/ql/Whereable.js +22 -11
  36. package/lib/ql/cds-ql.js +1 -1
  37. package/lib/req/cds-context.js +3 -3
  38. package/lib/req/response.js +8 -3
  39. package/lib/req/user.js +12 -2
  40. package/lib/srv/bindings.js +1 -2
  41. package/lib/srv/cds-serve.js +2 -1
  42. package/lib/srv/middlewares/trace.js +31 -15
  43. package/lib/srv/protocols/odata-v2-proxy.js +8 -8
  44. package/lib/srv/srv-handlers.js +26 -7
  45. package/lib/srv/srv-methods.js +2 -2
  46. package/lib/srv/srv-models.js +8 -3
  47. package/lib/utils/cds-test.js +7 -5
  48. package/lib/utils/cds-utils.js +3 -1
  49. package/lib/utils/tar.js +6 -3
  50. package/libx/_runtime/auth/strategies/JWT.js +1 -0
  51. package/libx/_runtime/auth/strategies/ias-auth.js +3 -2
  52. package/libx/_runtime/auth/strategies/mock.js +12 -1
  53. package/libx/_runtime/auth/strategies/xssecUtils.js +7 -8
  54. package/libx/_runtime/auth/strategies/xsuaa.js +1 -0
  55. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -2
  56. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -2
  57. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -1
  58. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +8 -0
  59. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +1 -1
  60. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +11 -2
  61. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +8 -8
  62. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +1 -1
  63. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/validator/ValueValidator.js +14 -14
  64. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +1 -0
  65. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ResourceJsonSerializer.js +3 -0
  66. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/UriHelper.js +2 -1
  67. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +3 -2
  68. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +7 -0
  69. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +0 -3
  70. package/libx/_runtime/cds-services/services/Service.js +11 -19
  71. package/libx/_runtime/cds-services/services/utils/columns.js +42 -40
  72. package/libx/_runtime/cds-services/util/assert.js +7 -1
  73. package/libx/_runtime/common/code-ext/WorkerReq.js +81 -0
  74. package/libx/_runtime/common/code-ext/config.js +13 -0
  75. package/libx/_runtime/common/code-ext/execute.js +113 -0
  76. package/libx/_runtime/common/code-ext/handlers.js +49 -0
  77. package/libx/_runtime/common/code-ext/worker.js +40 -0
  78. package/libx/_runtime/common/code-ext/workerQuery.js +45 -0
  79. package/libx/_runtime/common/code-ext/workerQueryExecutor.js +36 -0
  80. package/libx/_runtime/common/composition/data.js +5 -2
  81. package/libx/_runtime/common/composition/tree.js +2 -0
  82. package/libx/_runtime/common/generic/auth/restrict.js +1 -1
  83. package/libx/_runtime/common/generic/crud.js +4 -0
  84. package/libx/_runtime/common/generic/etag.js +3 -1
  85. package/libx/_runtime/common/generic/input.js +12 -14
  86. package/libx/_runtime/common/i18n/index.js +1 -1
  87. package/libx/_runtime/common/utils/cqn2cqn4sql.js +47 -22
  88. package/libx/_runtime/common/utils/path.js +5 -26
  89. package/libx/_runtime/common/utils/search2cqn4sql.js +16 -9
  90. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +19 -13
  91. package/libx/_runtime/db/data-conversion/post-processing.js +1 -1
  92. package/libx/_runtime/db/expand/expandCQNToJoin.js +7 -4
  93. package/libx/_runtime/db/expand/rawToExpanded.js +3 -2
  94. package/libx/_runtime/db/generic/input.js +2 -2
  95. package/libx/_runtime/db/generic/integrity.js +1 -0
  96. package/libx/_runtime/db/generic/virtual.js +1 -0
  97. package/libx/_runtime/db/query/read.js +3 -2
  98. package/libx/_runtime/db/utils/localized.js +1 -1
  99. package/libx/_runtime/fiori/generic/activate.js +7 -1
  100. package/libx/_runtime/fiori/generic/before.js +9 -1
  101. package/libx/_runtime/fiori/generic/edit.js +8 -1
  102. package/libx/_runtime/fiori/generic/new.js +2 -0
  103. package/libx/_runtime/fiori/generic/patch.js +2 -0
  104. package/libx/_runtime/fiori/generic/prepare.js +2 -0
  105. package/libx/_runtime/fiori/generic/read.js +16 -5
  106. package/libx/_runtime/fiori/generic/readOverDraft.js +2 -0
  107. package/libx/_runtime/fiori/lean-draft.js +505 -241
  108. package/libx/_runtime/fiori/utils/delete.js +2 -0
  109. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -5
  110. package/libx/_runtime/hana/pool.js +1 -1
  111. package/libx/_runtime/hana/search2cqn4sql.js +51 -51
  112. package/libx/_runtime/messaging/Outbox.js +1 -1
  113. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -0
  114. package/libx/_runtime/messaging/enterprise-messaging.js +2 -6
  115. package/libx/_runtime/messaging/file-based.js +1 -2
  116. package/libx/_runtime/messaging/outbox/OutboxRunner.js +1 -1
  117. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  118. package/libx/_runtime/messaging/service.js +0 -1
  119. package/libx/_runtime/remote/Service.js +1 -0
  120. package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +19 -3
  121. package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +0 -18
  122. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +0 -18
  123. package/libx/_runtime/sqlite/customBuilder/CustomSelectBuilder.js +0 -24
  124. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +2 -1
  125. package/libx/_runtime/sqlite/customBuilder/index.js +47 -32
  126. package/libx/odata/afterburner.js +23 -8
  127. package/libx/odata/cqn2odata.js +1 -1
  128. package/libx/odata/grammar.pegjs +3 -4
  129. package/libx/odata/index.js +5 -1
  130. package/libx/odata/parseToCqn.js +3 -3
  131. package/libx/odata/parser.js +1 -1
  132. package/libx/odata/utils.js +58 -1
  133. package/libx/rest/middleware/parse.js +26 -4
  134. package/package.json +1 -1
  135. package/server.js +1 -1
  136. package/libx/_runtime/sqlite/customBuilder/CustomDeleteBuilder.js +0 -17
  137. package/libx/_runtime/sqlite/customBuilder/CustomReferenceBuilder.js +0 -11
  138. package/libx/_runtime/sqlite/customBuilder/CustomUpdateBuilder.js +0 -17
  139. /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.log(`building module [${appFolder}] using [${this.constructor.name}]`)
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 the service
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 { src, dest } = this.task
97
- const modelPaths = this.resolveModel()
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
- this.logger._debug && this.logger.debug(`model: ${relativePaths(this.buildOptions.root, modelPaths).join(", ")}`)
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][ANNO_PERSISTENCE_JOURNAL] !== true) {
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 this._readTemplateAsJson(FILE_NAME_PACKAGE_JSON)
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 this._readTemplateAsJson(FILE_NAME_HDICONFIG)
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.logger.error(`'HANA plugin not found for file suffix [${Array.from(plugins).join(',')}]`)
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 this._readTemplateAsJson(FILE_NAME_HDINAMESPACE)
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 this._readTemplateAsJson(FILE_NAME_UNDEPLOY_JSON)
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
+ }
@@ -3,6 +3,9 @@
3
3
  "dependencies": {
4
4
  "@sap/hdi-deploy": "^4"
5
5
  },
6
+ "engines": {
7
+ "node": "^14 || ^16 || ^18"
8
+ },
6
9
  "scripts": {
7
10
  "start": "node node_modules/@sap/hdi-deploy/deploy.js"
8
11
  }
@@ -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(resources, root, path.join(dest, DEFAULT_TAR_NAME))
24
+ await this.writeTarFile(path.join(dest, DEFAULT_TAR_NAME), root, resources)
25
25
  }
26
26
 
27
- async writeTarFile(resources, root, tarFile) {
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) // REVISIT: tar.czfd was created for this case only -> it ensures the target's dir exists
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 model = await this.model()
17
- if (!model) {
18
- return
19
- }
20
- const allFiles = []
21
- const destExt = path.join(this.task.dest, 'ext')
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
- const csnFile = path.join(destExt, 'extension.csn')
34
- await this.compileToJson(extCsn, csnFile)
35
- allFiles.push(csnFile)
36
-
37
- // static i18n folder name as runtime does not use the CDS config of the extension project
38
- const i18n = await this.collectLanguageBundles(extCsn, path.join(destExt, 'i18n'))
39
- if (i18n) {
40
- allFiles.push(i18n.file)
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
- const files = Object.keys(await cds.deploy.resources(model))
44
- if (files.length > 0) {
45
- const dataDest = path.join(destExt, 'data')
46
- await Promise.all(
47
- files
48
- .filter(file => /\.csv$/.test(file))
49
- .map(csv => {
50
- const csvFile = path.join(dataDest, path.basename(csv))
51
- allFiles.push(csvFile)
52
- return this.copy(csv).to(csvFile)
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
- await new ResourcesTarBuilder(this).writeTarFile(allFiles, destExt, path.join(this.task.dest, 'extension.tgz'))
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("CDS build failed", "Invalid MTX sidecar configuration - \"cds.xt.ModelProviderService\": \"in-sidecar\" missing.")
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
- const _error = (e) => { cli.log(e.errors || e, { 'log-level': cds.env.log.levels.cli }); _exit(1) }
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 isEmpty = !Object.keys(vcapEnv).length;
75
- if (!isEmpty) {
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
- if (bindCallback) {
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={}) { // NOSONAR
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', ()=> shutdown()) // in case of server.close() was called, like in cds.test
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
- async function shutdown (sig) {
218
- if (shutdown.called) return; else shutdown.called = true // only do that once
219
- global.it || cds.watched || console.log() // blank line makes the ^C look pretty in terminals
220
- debug && debug(`${sig}, shutting down, calling ${cds.listeners('shutdown').length} listeners`)
221
- await Promise.all(cds.listeners('shutdown').map((fn) => fn()))
222
- if (process.env.NODE_ENV !== 'test' && !global.it) process.exit()
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