@sap/cds 5.7.5 → 5.8.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 (141) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/app/fiori/routes.js +1 -1
  3. package/bin/deploy/to-hana/cfUtil.js +251 -138
  4. package/bin/deploy/to-hana/gitUtil.js +55 -0
  5. package/bin/deploy/to-hana/hana.js +92 -93
  6. package/bin/deploy/to-hana/hdiDeployUtil.js +42 -27
  7. package/bin/deploy/to-hana/index.js +14 -13
  8. package/bin/mtx/in-cds.js +1 -0
  9. package/bin/serve.js +1 -1
  10. package/bin/version.js +1 -0
  11. package/lib/compile/cdsc.js +0 -6
  12. package/lib/compile/resolve.js +1 -1
  13. package/lib/compile/to/srvinfo.js +1 -1
  14. package/lib/core/classes.js +21 -1
  15. package/lib/env/index.js +3 -2
  16. package/lib/env/requires.js +4 -0
  17. package/lib/i18n/localize.js +5 -8
  18. package/lib/index.js +1 -0
  19. package/lib/log/errors.js +1 -1
  20. package/lib/ql/SELECT.js +2 -2
  21. package/lib/req/cds-context.js +1 -1
  22. package/lib/req/context.js +1 -1
  23. package/lib/serve/Transaction.js +9 -5
  24. package/lib/serve/index.js +13 -21
  25. package/lib/utils/tests.js +90 -66
  26. package/libx/_runtime/audit/generic/personal/modification.js +0 -8
  27. package/libx/_runtime/auth/index.js +7 -6
  28. package/libx/_runtime/auth/strategies/dwc.js +43 -0
  29. package/libx/_runtime/auth/utils.js +24 -0
  30. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -32
  31. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +12 -5
  32. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +7 -4
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +24 -3
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +43 -38
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +11 -5
  37. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +1 -2
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +0 -1
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
  40. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +9 -0
  41. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +17 -30
  42. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +12 -1
  43. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +2 -1
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +7 -6
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +2 -5
  46. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +19 -47
  47. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +4 -11
  48. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +7 -1
  49. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js +0 -3
  50. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -1
  51. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
  52. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
  53. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
  54. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  55. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -4
  56. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
  57. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
  58. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +60 -18
  59. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +7 -5
  60. package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
  61. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
  62. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
  63. package/libx/_runtime/cds-services/services/utils/columns.js +5 -1
  64. package/libx/_runtime/cds-services/services/utils/compareJson.js +15 -16
  65. package/libx/_runtime/cds-services/services/utils/differ.js +2 -8
  66. package/libx/_runtime/common/aspects/Association.js +16 -0
  67. package/libx/_runtime/common/composition/data.js +28 -37
  68. package/libx/_runtime/common/composition/delete.js +107 -58
  69. package/libx/_runtime/common/composition/index.js +2 -1
  70. package/libx/_runtime/common/composition/insert.js +13 -13
  71. package/libx/_runtime/common/composition/update.js +39 -34
  72. package/libx/_runtime/common/error/frontend.js +17 -2
  73. package/libx/_runtime/common/generic/auth.js +20 -85
  74. package/libx/_runtime/common/generic/crud.js +22 -1
  75. package/libx/_runtime/common/i18n/messages.properties +2 -1
  76. package/libx/_runtime/common/utils/cqn.js +2 -6
  77. package/libx/_runtime/common/utils/cqn2cqn4sql.js +95 -122
  78. package/libx/_runtime/common/utils/csn.js +14 -3
  79. package/libx/_runtime/common/utils/foreignKeyPropagations.js +18 -1
  80. package/libx/_runtime/common/utils/keys.js +2 -1
  81. package/libx/_runtime/common/utils/path.js +1 -1
  82. package/libx/_runtime/common/utils/resolveView.js +12 -4
  83. package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
  84. package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
  85. package/libx/_runtime/common/utils/vcap.js +27 -10
  86. package/libx/_runtime/db/data-conversion/post-processing.js +20 -13
  87. package/libx/_runtime/db/expand/expand-v2.js +21 -12
  88. package/libx/_runtime/db/expand/expandCQNToJoin.js +8 -6
  89. package/libx/_runtime/db/expand/index.js +3 -0
  90. package/libx/_runtime/db/generic/create.js +0 -10
  91. package/libx/_runtime/db/generic/index.js +3 -0
  92. package/libx/_runtime/db/generic/read.js +2 -24
  93. package/libx/_runtime/db/generic/rewrite.js +1 -3
  94. package/libx/_runtime/db/generic/update.js +1 -1
  95. package/libx/_runtime/db/query/delete.js +10 -4
  96. package/libx/_runtime/db/query/insert.js +3 -3
  97. package/libx/_runtime/db/query/read.js +4 -1
  98. package/libx/_runtime/db/query/update.js +5 -5
  99. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
  100. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
  101. package/libx/_runtime/db/sql-builder/index.js +3 -0
  102. package/libx/_runtime/db/utils/columns.js +5 -2
  103. package/libx/_runtime/db/utils/deep.js +6 -8
  104. package/libx/_runtime/db/utils/generateAliases.js +56 -6
  105. package/libx/_runtime/fiori/generic/before.js +73 -49
  106. package/libx/_runtime/fiori/generic/edit.js +14 -18
  107. package/libx/_runtime/fiori/generic/patch.js +8 -11
  108. package/libx/_runtime/fiori/generic/read.js +19 -16
  109. package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
  110. package/libx/_runtime/hana/Service.js +1 -1
  111. package/libx/_runtime/hana/conversion.js +10 -0
  112. package/libx/_runtime/hana/execute.js +33 -16
  113. package/libx/_runtime/hana/search.js +3 -3
  114. package/libx/_runtime/hana/search2cqn4sql.js +22 -21
  115. package/libx/_runtime/hana/searchToContains.js +1 -1
  116. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  117. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
  118. package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
  119. package/libx/_runtime/messaging/file-based.js +3 -1
  120. package/libx/_runtime/messaging/service.js +4 -1
  121. package/libx/_runtime/remote/utils/client.js +33 -20
  122. package/libx/_runtime/remote/utils/data.js +52 -11
  123. package/libx/_runtime/sqlite/Service.js +1 -1
  124. package/libx/_runtime/sqlite/conversion.js +10 -0
  125. package/libx/_runtime/types/api.js +2 -2
  126. package/libx/gql/resolvers/parse/ast/enrich.js +1 -0
  127. package/libx/odata/afterburner.js +29 -6
  128. package/libx/odata/cqn2odata.js +9 -0
  129. package/libx/odata/grammar.pegjs +49 -21
  130. package/libx/odata/index.js +2 -2
  131. package/libx/odata/parser.js +1 -1
  132. package/libx/odata/utils.js +2 -2
  133. package/libx/rest/RestAdapter.js +29 -1
  134. package/libx/rest/middleware/auth.js +1 -3
  135. package/libx/rest/middleware/parse.js +1 -0
  136. package/package.json +1 -1
  137. package/server.js +1 -1
  138. package/bin/deploy/to-hana/logger.js +0 -27
  139. package/bin/deploy/to-hana/runCommand.js +0 -113
  140. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +0 -37
  141. package/libx/_runtime/common/utils/auth.js +0 -16
@@ -1,90 +1,118 @@
1
-
2
- const {fse} = require('@sap/cds-foss');
1
+ const cp = require('child_process');
3
2
  const os = require('os');
4
3
  const path = require('path');
4
+ const util = require('util');
5
+
6
+ const { fse } = require('@sap/cds-foss');
5
7
 
6
8
  const { BuildTaskEngine, BuildTaskFactory } = require('../../build');
7
9
  const buildConstants = require('../../build/constants');
10
+
8
11
  const cds = require('../../../lib');
12
+ const LOG = cds.log ? cds.log('deploy') : console;
13
+
9
14
  const cfUtil = require('./cfUtil');
10
- const { defaultLogger, nullLogger } = require('./logger');
11
15
  const hdiDeployUtil = require('./hdiDeployUtil');
16
+ const gitUtil = require('./gitUtil');
12
17
  const mtaUtil = require('../../build/mtaUtil');
13
- const runCommand = require('./runCommand');
14
- const { bold, info } = require('../../utils/term');
15
18
 
19
+ const { bold, info } = require('../../utils/term');
16
20
 
17
21
  const IS_WIN = (os.platform() === 'win32');
18
22
  const UTF_8 = 'utf-8';
19
23
 
20
24
 
21
- const DEBUG = process.env.DEBUG;
22
-
23
25
  class HanaDeployer {
24
26
 
25
- async deploy(model, serviceName, noSave, tunnelAddress, buildTaskOptions, vcapFile, undeployWhitelist, hdiOptions = {}, logger = defaultLogger) { // NOSONAR
27
+ constructor() {
28
+ this.execAsync = util.promisify(cp.exec);
29
+ this.spawnSync = cp.spawnSync;
30
+ }
31
+
32
+
33
+ async deploy(model, serviceName, noSave, tunnelAddress, buildTaskOptions, vcapFile, undeployWhitelist, hdiOptions, bindCallback, logger = LOG) { // NOSONAR
34
+ hdiOptions = hdiOptions || {}
35
+ this.logger = logger;
26
36
 
27
- logger.log(`[cds.deploy] - ${bold('Starting deploy to SAP HANA ...')}`);
28
- logger.log();
37
+ this.logger.log(`${bold('Starting deploy to SAP HANA ...')}`);
38
+ if (bindCallback) {
39
+ this.logger.log('Using advanced mode');
40
+ }
41
+ this.logger.log();
29
42
 
30
43
  const projectPath = path.resolve(process.env._TEST_CWD || process.cwd());
31
44
 
32
- const { buildResults } = await this._build(buildTaskOptions, model, logger);
45
+ const { buildResults } = await this._build(buildTaskOptions, model);
33
46
 
34
47
  let vcapServices;
35
48
  if (vcapFile) {
36
- logger.log();
37
- logger.log(`[cds.deploy] - Using vcap file ${vcapFile} (beta feature).`);
49
+ this.logger.log();
50
+ this.logger.log(`Using vcap file ${vcapFile} (beta feature).`);
38
51
  vcapServices = await this._loadVCAPServices(vcapFile);
39
52
  }
40
53
 
41
54
  for (const buildResult of buildResults) {
55
+ let serviceKeyName;
42
56
  const currentModelFolder = buildResult.result.dest;
43
57
 
44
58
  if (undeployWhitelist) {
45
- logger.log('[cds.deploy] - Writing undeploy.json');
59
+ this.logger.log('Writing undeploy.json');
46
60
  await fse.writeJSON(path.join(currentModelFolder, 'undeploy.json'), undeployWhitelist);
47
61
  }
48
62
 
49
63
  if (vcapFile) {
50
64
  await fse.mkdirp(currentModelFolder);
51
65
  } else {
52
- const { cfServiceInstanceName, serviceKey } =
53
- await this._getOrCreateCFService(projectPath, currentModelFolder, serviceName, tunnelAddress, logger);
66
+ const { cfServiceInstanceName, cfServiceInstanceKeyName, serviceKey } =
67
+ await this._getOrCreateCFService(projectPath, currentModelFolder, serviceName, tunnelAddress);
68
+ serviceKeyName = cfServiceInstanceKeyName;
69
+ serviceName = serviceName || cfServiceInstanceName;
54
70
 
55
71
  vcapServices = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
56
72
 
57
- if (!noSave) {
58
- await this._addInstanceToDefaultEnvJson([currentModelFolder, projectPath], cfServiceInstanceName, serviceKey, logger);
73
+ if (!noSave && !bindCallback) {
74
+ await this._addInstanceToDefaultEnvJson([currentModelFolder, projectPath], cfServiceInstanceName, serviceKey);
59
75
  }
60
76
  }
61
77
 
62
78
  // Check if deployer is already installed, otherwise only install this one, not the rest of dependencies.
63
- if (!await hdiDeployUtil.findHdiDeployLib(currentModelFolder, logger)) {
64
- const { deployerName, deployerVersionSpec } = hdiDeployUtil
65
- logger.log(`[cds.deploy] - installing ${deployerName}`);
66
- await runCommand('npm', ['install', `${deployerName}@${deployerVersionSpec}`,
67
- (noSave ? '--no-save' : '--save-dev')], logger, {
79
+ if (!await hdiDeployUtil.findHdiDeployLib(currentModelFolder)) {
80
+ const { deployerName, deployerVersionSpec } = hdiDeployUtil;
81
+ this.logger.log(`Installing ${deployerName}`);
82
+
83
+ // spawn has not callback and cannot be promisified, using sync instead
84
+ this.spawnSync(`npm`, [`install`, `${deployerName}@${deployerVersionSpec}`, (noSave ? '--no-save' : '--save-dev')], {
68
85
  cwd: currentModelFolder,
69
86
  shell: IS_WIN,
70
87
  stdio: 'inherit',
71
- env: { NODE_ENV: 'development' } // for 'install --save-dev' to work, there must be no 'production' set
88
+ env: {
89
+ ...process.env,
90
+ NODE_ENV: 'development' // for 'install --save-dev' to work, there must be no 'production' set
91
+ }
72
92
  });
73
93
  }
74
94
 
75
- await hdiDeployUtil.deploy(currentModelFolder, vcapServices, hdiOptions, logger);
95
+ await hdiDeployUtil.deploy(currentModelFolder, vcapServices, hdiOptions);
96
+
97
+ if (bindCallback) {
98
+ const args = [path.relative(projectPath, buildResult.task.src)];
99
+ const options = { to: `${serviceName}:${serviceKeyName}`, kind: buildResult.task.for }
100
+ await bindCallback(args, options, LOG);
101
+ }
76
102
  }
77
103
 
78
- await this._addToGitignore(projectPath, 'default-env.json', logger);
104
+ if (!bindCallback) {
105
+ await gitUtil.ensureFileIsGitignored('default-env.json', projectPath);
106
+ }
79
107
 
80
- logger.log(`[cds.deploy] - If not already done, use ${info('cds add hana')} to configure the project for SAP HANA.\n`);
81
- logger.log(`[cds.deploy] - Done.`);
108
+ this.logger.log(`If not already done, use ${info('cds add hana')} to configure the project for SAP HANA.\n`);
109
+ this.logger.log(`Done.`);
82
110
 
83
111
  return { buildResults };
84
112
  }
85
113
 
86
114
 
87
- async _getOrCreateCFService(projectPath, currentModelFolder, serviceName, tunnelAddress, logger) {
115
+ async _getOrCreateCFService(projectPath, currentModelFolder, serviceName, tunnelAddress) {
88
116
  const modelName = path.basename(currentModelFolder);
89
117
 
90
118
  // get from param
@@ -93,77 +121,75 @@ class HanaDeployer {
93
121
  if (serviceName) {
94
122
  cfServiceInstanceName = serviceName;
95
123
  } else {
96
- const cfServiceDescriptor = await mtaUtil.getHanaDbModuleDescriptor(projectPath, modelName, logger);
124
+ const cfServiceDescriptor = await mtaUtil.getHanaDbModuleDescriptor(projectPath, modelName, this.logger);
97
125
  cfServiceInstanceName = cfServiceDescriptor.hdiServiceName;
98
126
  cfServiceInstanceMta = cfServiceDescriptor.hdiService
99
127
  }
100
128
 
101
- logger.log();
129
+ this.logger.log();
102
130
  this._validateServiceInstanceName(cfServiceInstanceName);
103
- logger.log(`[cds.deploy] - Using container ${bold(cfServiceInstanceName)}`);
131
+ this.logger.log(`Using container ${bold(cfServiceInstanceName)}`);
104
132
 
105
133
  let cfConfig = cfServiceInstanceMta && cfServiceInstanceMta.parameters && cfServiceInstanceMta.parameters.config;
106
- await this.createHanaService(cfServiceInstanceName, cfConfig, logger);
134
+ const serviceInstance = await this.createHanaService(cfServiceInstanceName, cfConfig);
107
135
 
108
136
  const cfServiceInstanceKeyName = `${cfServiceInstanceName}-key`;
109
- let serviceKey = await cfUtil.getServiceKey(cfServiceInstanceName, cfServiceInstanceKeyName);
110
- if (!serviceKey) {
111
- serviceKey = await cfUtil.createServiceKey(cfServiceInstanceName, cfServiceInstanceKeyName, logger);
112
- }
137
+ let serviceKey = await cfUtil.getOrCreateServiceKey(serviceInstance, cfServiceInstanceKeyName);
113
138
  this._validateServiceKey(serviceKey, cfServiceInstanceKeyName);
114
139
 
115
140
  if (tunnelAddress) {
116
- logger.log(`[cds.deploy] - Using tunnel address ${bold(tunnelAddress)} (beta feature)`);
141
+ this.logger.log(`Using tunnel address ${bold(tunnelAddress)} (beta feature)`);
117
142
  serviceKey = this._injectTunnelAddress(serviceKey, tunnelAddress)
118
143
  }
119
144
 
120
- return { cfServiceInstanceName, serviceKey }
145
+ return { cfServiceInstanceName, cfServiceInstanceKeyName, serviceKey, serviceInstance }
121
146
  }
122
147
 
123
- async createHanaService(instanceName, cfConfig, logger) {
148
+
149
+ async createHanaService(instanceName, cfConfig) {
124
150
  // hana or hanatrial, error if neither found
125
151
  try {
126
- return await cfUtil.createService('hana', 'hdi-shared', instanceName, cfConfig, logger);
152
+ return await cfUtil.getOrCreateService('hana', 'hdi-shared', instanceName, cfConfig);
127
153
  } catch (error) {
128
154
  if (error.command && /offering .* not found/i.test(error.command.stderr)) {
129
- logger.log(`[cds.deploy] - Falling back to 'hanatrial'`);
130
- return await cfUtil.createService('hanatrial', 'hdi-shared', instanceName, cfConfig, logger);
155
+ this.logger.log(`Falling back to 'hanatrial'`);
156
+ return await cfUtil.getOrCreateService('hanatrial', 'hdi-shared', instanceName, cfConfig);
131
157
  }
132
158
  else if (error.command && /no database/i.test(error.command.stderr)) {
133
- logger.log(`[cds.deploy] - No database connected to 'hana' service. Falling back to 'hanatrial'`);
134
- return await cfUtil.createService('hanatrial', 'hdi-shared', instanceName, cfConfig, logger);
159
+ this.logger.log(`No database connected to 'hana' service. Falling back to 'hanatrial'`);
160
+ return await cfUtil.getOrCreateService('hanatrial', 'hdi-shared', instanceName, cfConfig);
135
161
  }
136
162
  throw error;
137
163
  }
138
164
  }
139
165
 
140
166
 
141
-
142
167
  _validateServiceKey(serviceKey, cfServiceInstanceKey) {
143
168
  if (!serviceKey) {
144
- throw new Error(`[cds.deploy] - Could not create service key ${bold(cfServiceInstanceKey)}.`);
169
+ throw new Error(`Could not create service key ${bold(cfServiceInstanceKey)}.`);
145
170
  }
146
171
 
147
172
  const fields = ['schema', 'user', 'password', 'url'];
148
173
  for (const field of fields) {
149
174
  if (!serviceKey[field]) {
150
- throw new Error(`[cds.deploy] - Service key is missing mandatory field '${field}'. Make sure you are ${bold('not')} using a managed service.`);
175
+ throw new Error(`Service key is missing mandatory field '${field}'. Make sure you are ${bold('not')} using a managed service.`);
151
176
  }
152
177
  }
153
178
  }
154
179
 
155
180
 
156
- async _build(buildTaskOptions, model, logger) {
181
+ async _build(buildTaskOptions, model) {
157
182
  buildTaskOptions = buildTaskOptions || {
158
- root: process.env._TEST_CWD || process.cwd()
183
+ root: process.env._TEST_CWD || process.cwd(),
184
+ cli: true
159
185
  };
160
186
 
161
187
  if (typeof model === 'string') {
162
188
  model = [model];
163
189
  }
164
190
 
165
- logger.log(`[cds.deploy] - Creating build tasks`);
166
- const buildTaskFactory = new BuildTaskFactory((DEBUG ? logger : nullLogger), cds);
191
+ this.logger.log(`Creating build tasks`);
192
+ const buildTaskFactory = new BuildTaskFactory(null, cds);
167
193
  const allTasks = await buildTaskFactory.getTasks(buildTaskOptions);
168
194
 
169
195
  const hanaTasks = allTasks.filter((task => {
@@ -185,9 +211,9 @@ class HanaDeployer {
185
211
  });
186
212
  }
187
213
 
188
- logger.log(`[cds.deploy] - Running build`);
214
+ this.logger.log(`Running build`);
189
215
 
190
- const buildResults = await new BuildTaskEngine((DEBUG ? logger : nullLogger)).processTasks(hanaTasks, buildTaskOptions);
216
+ const buildResults = await new BuildTaskEngine().processTasks(hanaTasks, buildTaskOptions);
191
217
  return { buildResults, allTasks }
192
218
  }
193
219
 
@@ -206,7 +232,7 @@ class HanaDeployer {
206
232
  }
207
233
 
208
234
 
209
- async _addInstanceToDefaultEnvJson(currentFolders, serviceInstanceName, serviceKey, logger) {
235
+ async _addInstanceToDefaultEnvJson(currentFolders, serviceInstanceName, serviceKey) {
210
236
  for (const currentFolder of currentFolders) {
211
237
  let defaultEnvJson = {};
212
238
  const defaultEnvJsonPath = path.join(currentFolder, 'default-env.json');
@@ -230,7 +256,7 @@ class HanaDeployer {
230
256
  ...hanaEntry
231
257
  }
232
258
 
233
- logger.log(`[cds.deploy] - Writing ${defaultEnvJsonPath}`);
259
+ this.logger.log(`Writing ${defaultEnvJsonPath}`);
234
260
  await fse.outputJSON(defaultEnvJsonPath, defaultEnvJson, {
235
261
  spaces: 2
236
262
  });
@@ -238,42 +264,6 @@ class HanaDeployer {
238
264
  }
239
265
 
240
266
 
241
- async _addToGitignore(currentFolder, entry, logger) {
242
- const gitIgnorePath = path.join(currentFolder, '.gitignore');
243
- let entryMustBeAdded = true;
244
- try {
245
- const gitCheckCmd = await runCommand('git', ['check-ignore', entry], nullLogger);
246
- if (gitCheckCmd.code === 0) {
247
- // git verifies the chain of gitignore files, code === 0 file is ignored
248
- entryMustBeAdded = false;
249
- }
250
- } catch (err) {
251
- // git command not available or some problem occurred
252
- logger.warn(`[cds.deploy] - Error while calling git: ${err}`);
253
- }
254
-
255
- if (entryMustBeAdded) {
256
- let gitIgnore = '';
257
- try {
258
- gitIgnore = await fse.readFile(gitIgnorePath, UTF_8);
259
- if (gitIgnore.indexOf(entry) >= 0) {
260
- // entry exists in file
261
- return;
262
- }
263
- } catch (err) {
264
- // ignore file not found
265
- }
266
-
267
- logger.log(`[cds.deploy] - Adding ${entry} to ${gitIgnorePath}`);
268
- gitIgnore = `${gitIgnore.trim()}
269
-
270
- # added by cds deploy
271
- ${entry}
272
- `;
273
- await fse.outputFile(gitIgnorePath, gitIgnore);
274
- }
275
- }
276
-
277
267
  _getVCAPServicesEntry(serviceInstanceName, serviceKey) {
278
268
  return {
279
269
  hana: [
@@ -290,10 +280,11 @@ ${entry}
290
280
  _validateServiceInstanceName(serviceInstanceName) {
291
281
  // valid service name chars: alpha-numeric, hyphens, and underscores
292
282
  if (/[^\w-_]+/g.exec(serviceInstanceName)) {
293
- throw new Error(`[cds.deploy] - Service name ${serviceInstanceName} must only contain alpha-numeric, hyphens, and underscores.`);
283
+ throw new Error(`Service name ${serviceInstanceName} must only contain alpha-numeric, hyphens, and underscores.`);
294
284
  }
295
285
  }
296
286
 
287
+
297
288
  _injectTunnelAddress(serviceKey, tunnelAddress) {
298
289
  if (!/\w+:\d+/.test(tunnelAddress)) {
299
290
  throw new Error(`Invalid tunnel address '${tunnelAddress}' - must be in form 'host:port'`)
@@ -308,6 +299,14 @@ ${entry}
308
299
  return serviceKey
309
300
  }
310
301
 
302
+
303
+ async readFileSafely(file) {
304
+ try {
305
+ return await fse.readFile(file, 'utf8');
306
+ } catch (err) {
307
+ return '';
308
+ }
309
+ }
311
310
  }
312
311
 
313
312
  module.exports = new HanaDeployer();
@@ -4,7 +4,9 @@ const util = require('util');
4
4
 
5
5
  const execAsync = util.promisify(cp.exec);
6
6
 
7
- const { nullLogger } = require('./logger');
7
+ const cds = require('../../../lib');
8
+ const { SILENT } = cds.log.levels;
9
+ const LOG = cds.log ? cds.log('deploy') : console;
8
10
 
9
11
  class HdiDeployUtil {
10
12
 
@@ -14,41 +16,49 @@ class HdiDeployUtil {
14
16
  this.deployerVersionSpec = '^4'
15
17
  }
16
18
 
17
- async deploy(dbDir, vcapServices, options = {}, logger = nullLogger) {
18
- logger.log();
19
- logger.log(`[cds.deploy] - Deploying to HANA from ${dbDir}`);
19
+ async deploy(dbDir, vcapServices, options = {}) {
20
+ LOG.log();
21
+ LOG.log(`Deploying to HANA from ${dbDir}`);
20
22
 
21
- let deployerEnv = JSON.parse(JSON.stringify(process.env)); // deep copy
22
- const hdiDeployLib = await this._getHdiDeployLib(dbDir, logger);
23
- if (hdiDeployLib.clean_env) {
23
+ const hdiDeployLib = await this._getHdiDeployLib(dbDir);
24
+
25
+ let deployerEnv = JSON.parse(JSON.stringify(process.env));
26
+ const hdiDeployOptions = deployerEnv.HDI_DEPLOY_OPTIONS ? JSON.parse(deployerEnv.HDI_DEPLOY_OPTIONS) : {};
27
+ delete hdiDeployOptions.root;
28
+
29
+ if (typeof hdiDeployLib.clean_env === 'function') {
24
30
  deployerEnv = hdiDeployLib.clean_env(deployerEnv);
25
31
  }
26
32
 
27
33
  deployerEnv.VCAP_SERVICES = JSON.stringify(vcapServices);
28
34
 
29
35
  if (options.autoUndeploy) {
30
- logger.log(`[cds.deploy] - Hdi deployer automatically undeploys deleted resources using --auto-undeploy.`);
31
- deployerEnv.HDI_DEPLOY_OPTIONS = JSON.stringify({
32
- 'auto_undeploy': true
33
- });
36
+ LOG.log(`Hdi deployer automatically undeploys deleted resources using --auto-undeploy.`);
37
+ hdiDeployOptions.auto_undeploy = true;
38
+ }
39
+
40
+ if (Object.entries(hdiDeployOptions).length > 0) {
41
+ deployerEnv.HDI_DEPLOY_OPTIONS = JSON.stringify(hdiDeployOptions);
34
42
  }
35
43
 
36
- await this._executeDeploy(dbDir, deployerEnv, logger);
44
+ await this._executeDeploy(dbDir, deployerEnv);
37
45
  }
38
46
 
47
+
39
48
  async findHdiDeployLib(cwd) {
40
49
  const searchPaths = await this._npmSearchPaths(cwd)
41
50
  try {
42
- return require.resolve(path.join(this.deployerName, 'library'), {paths: searchPaths});
51
+ return require.resolve(path.join(this.deployerName, 'library'), { paths: searchPaths });
43
52
  } catch (err) {
44
53
  // no luck
45
54
  }
46
55
  }
47
56
 
48
- async _getHdiDeployLib(dbDir, logger) {
57
+
58
+ async _getHdiDeployLib(dbDir) {
49
59
  let hdiDeployLib = this.hdiDeployLibs.get(dbDir)
50
60
  if (!hdiDeployLib) {
51
- hdiDeployLib = await this._loadHdiDeployLib(dbDir, logger);
61
+ hdiDeployLib = await this._loadHdiDeployLib(dbDir);
52
62
  this.hdiDeployLibs.set(dbDir, hdiDeployLib);
53
63
  }
54
64
 
@@ -56,46 +66,51 @@ class HdiDeployUtil {
56
66
  }
57
67
 
58
68
 
59
- async _loadHdiDeployLib(cwd, logger = nullLogger) {
69
+ async _loadHdiDeployLib(cwd) {
60
70
  const libPath = await this.findHdiDeployLib(cwd)
61
71
  if (!libPath) {
62
72
  const searchPaths = await this._npmSearchPaths(cwd)
63
- throw new Error(`[cds.deploy] - Required library '${this.deployerName}' not found in
73
+ throw new Error(`Required library '${this.deployerName}' not found in
64
74
  ${searchPaths.join('\n ')}
65
75
  Add it either as a devDependency using 'npm install -D ${this.deployerName}' or install it globally using 'npm install -g ${this.deployerName}'.`);
66
76
  }
67
77
 
68
- logger.log(`[cds.deploy] - Using HDI deployer from ${libPath}`)
78
+ LOG.log(`Using HDI deployer from ${libPath}`)
69
79
 
70
80
  // let any error go through and abort deploy
71
81
  return require(libPath);
72
82
  }
73
83
 
74
84
 
75
- async _npmSearchPaths (cwd) {
85
+ async _npmSearchPaths(cwd) {
76
86
  const npmRootCall = await execAsync('npm root -g');
77
87
  const globalNodeModules = npmRootCall.stdout.toString().trim();
78
88
  return [cwd, globalNodeModules]
79
89
  }
80
90
 
81
91
 
82
- async _executeDeploy(dbDir, env, logger = nullLogger) {
83
- const hdiDeployLib = await this._getHdiDeployLib(dbDir, logger);
92
+ async _executeDeploy(dbDir, env) {
93
+ const hdiDeployLib = await this._getHdiDeployLib(dbDir);
84
94
  return new Promise((resolve, reject) => {
95
+ const callbacks = {
96
+ stderrCB: error => LOG.error(error.toString())
97
+ }
98
+ if (LOG.level !== SILENT) {
99
+ callbacks.stdoutCB = (data) => console.log(data.toString());
100
+ }
101
+
85
102
  hdiDeployLib.deploy(dbDir, env, (error, response) => {
86
103
  if (error) {
87
104
  return reject(error);
88
105
  }
89
106
  if (response && response.exitCode && response.exitCode !== 0) {
90
- let message = `[cds.deploy] - HDI deployment failed with exit code ${response.exitCode}`
91
- if (response.signal) message += `. ${response.signal}`
107
+ let message = `HDI deployment failed with exit code ${response.exitCode}`
108
+ if (response.signal) message += `. ${response.signal}`
92
109
  return reject(new Error(message));
93
110
  }
94
111
  return resolve();
95
- }, {
96
- stdoutCB: data => logger.debug(data.toString()),
97
- stderrCB: error => logger.error(error.toString())
98
- });
112
+ }, callbacks
113
+ );
99
114
  });
100
115
  }
101
116
  }
@@ -1,15 +1,16 @@
1
- exports.deploy = (_model, _dbSpecificParameter, {
2
- 'no-save':no_save,
3
- 'auto-undeploy': autoUndeploy = false,
4
- 'tunnel-address': tunnelAddress,
5
- 'vcap-file': vcapFile
6
- }) => {
7
- const hanaDeployer = require('./hana');
1
+ exports.deploy = (_model, _dbSpecificParameter, {
2
+ 'no-save': no_save,
3
+ 'auto-undeploy': autoUndeploy = false,
4
+ 'tunnel-address': tunnelAddress,
5
+ 'vcap-file': vcapFile,
6
+ }, bindCallback) => {
7
+ const hanaDeployer = require('./hana');
8
8
 
9
- return hanaDeployer.deploy (
10
- _model, _dbSpecificParameter,
11
- no_save, tunnelAddress,
12
- null, vcapFile, null,
13
- { autoUndeploy }
14
- )
9
+ return hanaDeployer.deploy(
10
+ _model, _dbSpecificParameter,
11
+ no_save, tunnelAddress,
12
+ null, vcapFile, null,
13
+ { autoUndeploy },
14
+ bindCallback
15
+ )
15
16
  }
package/bin/mtx/in-cds.js CHANGED
@@ -2,6 +2,7 @@ const cds = require('../build/cds')
2
2
  if (cds.requires.multitenancy) try {
3
3
  const mtx = module.exports = require ('@sap/cds-mtx')()
4
4
  mtx.inject (cds)
5
+ cds.on('served', () => cds.emit('mtx'))
5
6
  } catch(e) {
6
7
  if (e.code === 'MODULE_NOT_FOUND') throw new Error('Error serving MTX APIs: @sap/cds-mtx is not installed')
7
8
  else throw e
package/bin/serve.js CHANGED
@@ -194,7 +194,7 @@ function _local_server_js() {
194
194
  if (server_js) {
195
195
  log && log ('Loading server from', { file: local(server_js) })
196
196
  const fn = require (server_js)
197
- return typeof fn === 'function' ? fn : cds.error `${local(server_js)} must export a function`
197
+ return typeof fn === 'function' ? fn : cds.server
198
198
  }
199
199
  }
200
200
 
package/bin/version.js CHANGED
@@ -54,6 +54,7 @@ function info(o) {
54
54
  const main = _findPackage (require.main.filename)
55
55
  return {
56
56
  ..._versions4(main, {}, true), // usually sap/cds-dk or sap/cds
57
+ ..._versions4('@sap/cds-dk', {}, null, o), // make sure cds-dk is there, cds-maven-plugin uses it
57
58
  ..._versions4('@sap/eslint-plugin-cds', {}, null, o),
58
59
  ..._versions4(process.cwd(), {}, null, o),
59
60
  ..._versions4('..', {}, null, o),
@@ -3,12 +3,6 @@ const constraints = {
3
3
  assertIntegrity: features.assert_integrity,
4
4
  assertIntegrityType: features.assert_integrity_type
5
5
  }
6
- // REVISIT: remove with compiler ^2.11
7
- const { assertIntegrity: ai, assertIntegrityType: ait } = constraints
8
- if ((typeof ai === 'string' && ai.match(/individual/i)) || (ait && ait.match(/db/i))) {
9
- cdsc.beta = cdsc.beta || {}
10
- cdsc.beta.foreignKeyConstraints = true
11
- }
12
6
  const compile = require ('@sap/cds-compiler')
13
7
  const _4cdsc = Symbol('_4cdsc')
14
8
 
@@ -27,7 +27,7 @@ module.exports = exports = function cds_resolve (model, o={}) { // NOSONAR
27
27
  if (id in cached && !o.skipModelCache) return cached[id]
28
28
 
29
29
  // expand @sap/cds by cds.home
30
- if (id.startsWith('@sap/cds')) id = global.cds.home +'/'+ id.slice(8)
30
+ if (id.startsWith('@sap/cds/')) id = global.cds.home + id.slice(8)
31
31
 
32
32
  // fetch file with .cds/.csn suffix as is
33
33
  if (/\.(csn|cds)$/.test(id)) try {
@@ -37,7 +37,7 @@ module.exports = (model, options={}) => {
37
37
  function _makeNode(service) {
38
38
  return {
39
39
  name: service.name,
40
- urlPath: _url4 (cds.serve.path4(service)),
40
+ urlPath: _url4 (cds.service.path4(service)),
41
41
  destination: 'srv-api', // the name to register in xs-app.json
42
42
  runtime: 'Node.js',
43
43
  location: service.$location
@@ -23,7 +23,27 @@ class any {
23
23
  class type extends any {}
24
24
  class action extends any {}
25
25
  class context extends any {}
26
- class service extends context {}
26
+
27
+ class service extends context {
28
+
29
+ get path() { return super.path = service.path4(this) }
30
+
31
+ /**
32
+ * Resolve a service endpoint path to mount it to as follows...
33
+ * Use _path or def[@path] if given with leading '/' prepended if necessary.
34
+ * Otherwise, use the service definition name with stripped 'Service'
35
+ */
36
+ static path4 (srv, _path = (srv.definition || srv)['@path']) {
37
+ if (_path) return _path.startsWith('/') ? _path : '/'+_path
38
+ else return '/' + ( // return a sluggified variant of the service's name
39
+ /[^.]+$/.exec(srv.name)[0] //> my.very.CatalogService --> CatalogService
40
+ .replace(/Service$/,'') //> CatalogService --> Catalog
41
+ .replace(/_/g,'-') //> foo_bar_baz --> foo-bar-baz
42
+ .replace(/([a-z0-9])([A-Z])/g, (_,c,C) => c+'-'+C) //> ODataFooBarX9 --> OData-Foo-Bar-X9
43
+ .toLowerCase() //> FOO --> foo
44
+ )
45
+ }
46
+ }
27
47
 
28
48
  class array extends type { is(kind) { return kind === 'array' || super.is(kind) }}
29
49
  class aspect extends type { is(kind) { return kind === 'aspect' || super.is(kind) }}
package/lib/env/index.js CHANGED
@@ -338,7 +338,8 @@ function _merge (dst, src, _profiles, _cloned, _profiles_only = false) {
338
338
  const v = pd.value
339
339
  if (typeof v === 'object' && !Array.isArray(v)) {
340
340
  if (!dst[p]) dst[p] = {}; else if (_cloned) dst[p] = _cloned(dst[p])
341
- _merge (dst[p], v, _profiles, _cloned, _profiles_only)
341
+ if (typeof dst[p] !== 'object') dst[p] = v
342
+ else _merge (dst[p], v, _profiles, _cloned, _profiles_only)
342
343
  continue
343
344
  }
344
345
 
@@ -453,7 +454,7 @@ function _readJson (file) {
453
454
  }
454
455
 
455
456
  function _determineProfilesFrom (env = process.env) {
456
- if (env.NODE_ENV !== 'production' && !/\bdevelopment\b/.test(env.CDS_ENV)) {
457
+ if (env.NODE_ENV !== 'production' && !/\b(development|production)\b/.test(env.CDS_ENV)) {
457
458
  if (env.CDS_ENV) env.CDS_ENV += ',development'
458
459
  else env.CDS_ENV = 'development'
459
460
  }
@@ -31,6 +31,9 @@ module.exports = {
31
31
  "xsuaa-auth": {
32
32
  strategy: 'xsuaa',
33
33
  },
34
+ "dwc-auth": {
35
+ strategy: 'dwc',
36
+ },
34
37
  destinations: {
35
38
  vcap: {
36
39
  label: 'destination'
@@ -93,6 +96,7 @@ module.exports = {
93
96
  },
94
97
  "enterprise-messaging-http": {
95
98
  outbox: {},
99
+ deployForProvider: true,
96
100
  impl: `${_runtime}/messaging/enterprise-messaging.js`,
97
101
  vcap: { label: "enterprise-messaging" },
98
102
  },