@sap/cds-dk 6.8.2 → 7.0.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 (218) hide show
  1. package/bin/activate.js +15 -7
  2. package/bin/add.js +29 -15
  3. package/bin/bind.js +49 -116
  4. package/bin/build.js +114 -0
  5. package/bin/cds.js +67 -24
  6. package/bin/compile/index.js +2 -2
  7. package/bin/deploy/index.js +46 -39
  8. package/bin/deploy/to-hana.js +11 -8
  9. package/bin/deploy/to-postgres.js +8 -2
  10. package/bin/deploy/to-sqlite.js +0 -1
  11. package/bin/env.js +75 -41
  12. package/bin/eval.js +3 -3
  13. package/bin/extend.js +17 -10
  14. package/bin/help.js +22 -21
  15. package/bin/import.js +10 -7
  16. package/bin/init.js +38 -17
  17. package/bin/login.js +69 -55
  18. package/bin/logout.js +12 -9
  19. package/bin/migrate.js +10 -4
  20. package/bin/pull.js +12 -5
  21. package/bin/push.js +24 -13
  22. package/bin/repl.js +1 -0
  23. package/bin/run.js +25 -0
  24. package/bin/subscribe.js +13 -8
  25. package/bin/unsubscribe.js +15 -8
  26. package/bin/upgrade.js +15 -9
  27. package/bin/util/log.js +24 -0
  28. package/bin/version.js +124 -0
  29. package/bin/watch.js +1 -35
  30. package/bin/watched.js +39 -0
  31. package/lib/bind/KubernetesApiAccessor.js +2 -2
  32. package/lib/bind/bindingManager.js +1 -1
  33. package/lib/bind/bindingUtil.js +0 -9
  34. package/lib/bind/cfKeyProvider.js +10 -72
  35. package/lib/bind/index.js +114 -0
  36. package/lib/bind/kubernetesCredentialsProvider.js +2 -2
  37. package/lib/bind/userCdsrcJsonStorageProvider.js +7 -44
  38. package/lib/build/buildTaskEngine.js +355 -0
  39. package/lib/build/buildTaskFactory.js +284 -0
  40. package/lib/build/buildTaskHandler.js +240 -0
  41. package/lib/build/buildTaskProvider.js +22 -0
  42. package/lib/build/buildTaskProviderFactory.js +182 -0
  43. package/lib/build/constants.js +56 -0
  44. package/lib/build/index.js +21 -0
  45. package/lib/build/provider/buildTaskHandlerEdmx.js +83 -0
  46. package/lib/build/provider/buildTaskHandlerFeatureToggles.js +132 -0
  47. package/lib/build/provider/buildTaskHandlerInternal.js +228 -0
  48. package/lib/build/provider/buildTaskProviderInternal.js +370 -0
  49. package/lib/build/provider/fiori/index.js +167 -0
  50. package/lib/build/provider/hana/2migration.js +179 -0
  51. package/lib/build/provider/hana/index.js +522 -0
  52. package/lib/build/provider/hana/migrationtable.js +472 -0
  53. package/lib/build/provider/hana/template/.hdiconfig-all +163 -0
  54. package/lib/build/provider/hana/template/.hdiconfig-cloud +137 -0
  55. package/lib/build/provider/hana/template/.hdinamespace +4 -0
  56. package/lib/build/provider/hana/template/package.json +12 -0
  57. package/lib/build/provider/hana/template/undeploy.json +5 -0
  58. package/lib/build/provider/java/index.js +93 -0
  59. package/lib/build/provider/java-cf/index.js +1 -0
  60. package/lib/build/provider/mtx/index.js +37 -0
  61. package/lib/build/provider/mtx/resourcesTarBuilder.js +94 -0
  62. package/lib/build/provider/mtx-extension/index.js +131 -0
  63. package/lib/build/provider/mtx-sidecar/index.js +138 -0
  64. package/lib/build/provider/node-cf/index.js +1 -0
  65. package/lib/build/provider/nodejs/index.js +178 -0
  66. package/lib/build/util.js +263 -0
  67. package/lib/cds.js +9 -21
  68. package/lib/client/auth_manager.js +39 -16
  69. package/lib/client/helper/dependency_helper.js +7 -6
  70. package/lib/client/push.js +2 -1
  71. package/lib/client/settings_manager.js +10 -39
  72. package/lib/deploy/to-hana/hana.js +273 -0
  73. package/lib/deploy/to-hana/hdiDeployUtil.js +68 -0
  74. package/lib/deploy/to-hana/mtaUtil.js +171 -0
  75. package/lib/import/asyncapi/importAsyncAPI.js +3 -2
  76. package/lib/import/asyncapi/index.js +5 -2
  77. package/lib/import/asyncapi/parserContext.js +67 -26
  78. package/lib/import/asyncapi/utils.js +34 -3
  79. package/lib/import/common.js +40 -30
  80. package/lib/import/importapi.js +36 -35
  81. package/lib/index.js +12 -17
  82. package/lib/init/constants.js +36 -71
  83. package/lib/init/index.js +18 -52
  84. package/lib/init/template/_merging/registry-mta.js +21 -3
  85. package/lib/init/template/approuter/files/mta.yaml.hbs +29 -29
  86. package/lib/init/template/approuter/files/values.yaml.hbs +5 -5
  87. package/lib/init/template/approuter/files/xs-app.json.hbs +8 -2
  88. package/lib/init/template/approuter/index.js +7 -8
  89. package/lib/init/template/auditlog/files/mta.yaml.hbs +5 -5
  90. package/lib/init/template/auditlog/files/package.json.hbs +13 -13
  91. package/lib/init/template/auditlog/index.js +3 -3
  92. package/lib/init/template/cf-manifest/files/manifest.yml.hbs +22 -22
  93. package/lib/init/template/cf-manifest/files/services-manifest.yml.hbs +3 -3
  94. package/lib/init/template/connectivity/files/mta.yaml.hbs +14 -0
  95. package/lib/init/template/connectivity/files/package.json.hbs +17 -0
  96. package/lib/init/template/connectivity/index.js +21 -4
  97. package/lib/init/template/data/index.js +5 -1
  98. package/lib/init/template/destination/files/mta.yaml.hbs +14 -0
  99. package/lib/init/template/destination/files/package.json.hbs +17 -0
  100. package/lib/init/template/{destinations → destination}/index.js +19 -13
  101. package/lib/init/template/enterprise-messaging/files/event-mesh.json.hbs +3 -3
  102. package/lib/init/template/enterprise-messaging/files/mta.yaml.hbs +5 -5
  103. package/lib/init/template/enterprise-messaging/files/package.json.hbs +9 -9
  104. package/lib/init/template/enterprise-messaging/files/xs-security.json.hbs +19 -0
  105. package/lib/init/template/enterprise-messaging/index.js +27 -6
  106. package/lib/init/template/enterprise-messaging-shared/files/event-mesh.json.hbs +3 -3
  107. package/lib/init/template/enterprise-messaging-shared/files/mta.yaml.hbs +5 -5
  108. package/lib/init/template/enterprise-messaging-shared/files/package.json.hbs +9 -9
  109. package/lib/init/template/enterprise-messaging-shared/index.js +4 -5
  110. package/lib/init/template/extensibility/files/package.json.hbs +10 -10
  111. package/lib/init/template/extensibility/index.js +8 -9
  112. package/lib/init/template/file-based-messaging/files/package.json.hbs +9 -9
  113. package/lib/init/template/file-based-messaging/index.js +3 -4
  114. package/lib/init/template/hana/files/db/package.json +1 -1
  115. package/lib/init/template/hana/files/mta.yaml.hbs +21 -21
  116. package/lib/init/template/hana/files/package.json.hbs +16 -22
  117. package/lib/init/template/hana/files/values.yaml.hbs +1 -1
  118. package/lib/init/template/hana/index.js +5 -15
  119. package/lib/init/template/helm/files/Chart.yaml.hbs +4 -4
  120. package/lib/init/template/helm/files/values.yaml.hbs +6 -6
  121. package/lib/init/template/helm/index.js +5 -10
  122. package/lib/init/template/helm/subcharts/content-deployment/Chart.yaml +2 -0
  123. package/lib/init/template/helm/subcharts/content-deployment/templates/_helpers.tpl +37 -31
  124. package/lib/init/template/helm/subcharts/content-deployment/templates/job.yaml +13 -14
  125. package/lib/init/template/helm/subcharts/content-deployment/templates/secret.yaml +2 -2
  126. package/lib/init/template/helm/subcharts/content-deployment/templates/service-account.yaml +1 -1
  127. package/lib/init/template/helm/subcharts/content-deployment/templates/service-binding.yaml +3 -3
  128. package/lib/init/template/helm/subcharts/content-deployment/values.md +116 -116
  129. package/lib/init/template/helm/subcharts/service-instance/Chart.yaml +2 -0
  130. package/lib/init/template/helm/subcharts/service-instance/templates/_helpers.tpl +9 -9
  131. package/lib/init/template/helm/subcharts/service-instance/templates/serviceinstance.yaml +2 -2
  132. package/lib/init/template/helm/subcharts/service-instance/values.md +15 -15
  133. package/lib/init/template/helm/subcharts/web-application/Chart.yaml +2 -0
  134. package/lib/init/template/helm/subcharts/web-application/templates/NOTES.txt +15 -0
  135. package/lib/init/template/helm/subcharts/web-application/templates/_helpers.tpl +23 -23
  136. package/lib/init/template/helm/subcharts/web-application/templates/api-rule.yaml +4 -4
  137. package/lib/init/template/helm/subcharts/web-application/templates/deployment.yaml +17 -17
  138. package/lib/init/template/helm/subcharts/web-application/templates/network-policy.yaml +24 -3
  139. package/lib/init/template/helm/subcharts/web-application/templates/pod-disruption-budget.yaml +3 -3
  140. package/lib/init/template/helm/subcharts/web-application/templates/secret.yaml +2 -2
  141. package/lib/init/template/helm/subcharts/web-application/templates/service-account.yaml +1 -1
  142. package/lib/init/template/helm/subcharts/web-application/templates/service-binding.yaml +3 -3
  143. package/lib/init/template/helm/subcharts/web-application/templates/service.yaml +3 -3
  144. package/lib/init/template/helm/subcharts/web-application/values.md +141 -123
  145. package/lib/init/template/helm/subcharts/web-application/values.schema.json +7 -0
  146. package/lib/init/template/helm/subcharts/web-application/values.yaml +1 -0
  147. package/lib/init/template/html5-repo/index.js +4 -5
  148. package/lib/init/template/java/index.js +5 -5
  149. package/lib/init/template/kibana-logging/files/mta.yaml.hbs +5 -5
  150. package/lib/init/template/kibana-logging/files/package.json.hbs +9 -9
  151. package/lib/init/template/kibana-logging/index.js +3 -4
  152. package/lib/init/template/lint/index.js +3 -3
  153. package/lib/init/template/mta/files/mta.yaml.hbs +14 -14
  154. package/lib/init/template/mta/files/package.json +5 -0
  155. package/lib/init/template/mta/index.js +12 -13
  156. package/lib/init/template/mtx/index.js +2 -2
  157. package/lib/init/template/multitenancy/files/mta.yaml.hbs +29 -27
  158. package/lib/init/template/multitenancy/files/package.json.hbs +17 -14
  159. package/lib/init/template/multitenancy/files/package.sidecar.json.hbs +7 -7
  160. package/lib/init/template/multitenancy/files/values.yaml.hbs +19 -19
  161. package/lib/init/template/multitenancy/index.js +14 -16
  162. package/lib/init/template/nodejs/files/package.json.hbs +1 -1
  163. package/lib/init/template/nodejs/index.js +5 -8
  164. package/lib/init/template/redis-messaging/files/mta.yaml.hbs +5 -5
  165. package/lib/init/template/redis-messaging/files/package.json.hbs +9 -9
  166. package/lib/init/template/redis-messaging/index.js +4 -5
  167. package/lib/init/template/sample/files/app/_i18n/i18n.properties +43 -0
  168. package/lib/init/template/sample/files/app/_i18n/i18n_de.properties +14 -0
  169. package/lib/init/template/sample/files/app/admin-books/fiori-service.cds +114 -0
  170. package/lib/init/template/sample/files/app/admin-books/webapp/Component.js +8 -0
  171. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n.properties +11 -0
  172. package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +129 -0
  173. package/lib/init/template/sample/files/app/appconfig/fioriSandboxConfig.json +57 -0
  174. package/lib/init/template/sample/files/app/common.cds +263 -0
  175. package/lib/init/template/sample/files/app/index.html +30 -0
  176. package/lib/init/template/sample/files/app/services.cds +5 -0
  177. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Authors.csv +5 -0
  178. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Books.csv +6 -0
  179. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Books_texts.csv +5 -0
  180. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Genres.csv +16 -0
  181. package/lib/init/template/sample/files/db/schema.cds +31 -0
  182. package/lib/init/template/sample/files/nodejs/admin-service.js +12 -0
  183. package/lib/init/template/sample/files/nodejs/cat-service.js +26 -0
  184. package/lib/init/template/sample/files/srv/admin-service.cds +5 -0
  185. package/lib/init/template/sample/files/srv/cat-service.cds +16 -0
  186. package/lib/init/template/sample/index.js +24 -0
  187. package/lib/init/template/sample-tiny/index.js +39 -0
  188. package/lib/init/template/samples/index.js +5 -56
  189. package/lib/init/template/sqlite/files/package.json.hbs +13 -13
  190. package/lib/init/template/sqlite/index.js +2 -2
  191. package/lib/init/template/templateBase.js +24 -1
  192. package/lib/init/template/toggles/files/package.json.hbs +10 -10
  193. package/lib/init/template/toggles/index.js +3 -3
  194. package/lib/init/template/typer/files/gitignore +1 -0
  195. package/lib/init/template/typer/files/package.json +8 -0
  196. package/lib/init/template/typer/files/xsconfig.json +9 -0
  197. package/lib/init/template/typer/index.js +36 -0
  198. package/lib/init/template/xsuaa/files/mta.yaml.hbs +12 -12
  199. package/lib/init/template/xsuaa/files/package.json.hbs +13 -13
  200. package/lib/init/template/xsuaa/files/values.yaml.hbs +8 -8
  201. package/lib/init/template/xsuaa/index.js +8 -19
  202. package/lib/init/util/merge.js +73 -59
  203. package/lib/init/util/mvn.js +21 -17
  204. package/lib/init/util/projectReader.js +49 -87
  205. package/lib/init/util/xml.js +14 -23
  206. package/lib/util/cfUtil.js +366 -0
  207. package/lib/util/command.js +3 -3
  208. package/lib/util/gitUtil.js +55 -0
  209. package/lib/util/version.js +27 -0
  210. package/npm-shrinkwrap.json +339 -277
  211. package/package.json +6 -6
  212. package/CHANGELOG.md +0 -1453
  213. package/bin/build/all.js +0 -88
  214. package/bin/build/index.js +0 -82
  215. package/lib/compile/index.js +0 -14
  216. /package/lib/init/template/{samples → sample-tiny}/files/nodejs/db/data/my.bookshop-Books.csv +0 -0
  217. /package/lib/init/template/{samples → sample-tiny}/files/nodejs/db/data-model.cds +0 -0
  218. /package/lib/init/template/{samples → sample-tiny}/files/nodejs/srv/cat-service.cds.hbs +0 -0
@@ -6,11 +6,11 @@ const axios = require('axios');
6
6
  const cds = require('../cds');
7
7
  const DEBUG = cds.debug('cli');
8
8
  const { requireGlobal } = require('./helper/dependency_helper');
9
- const { getAppFromSuggestions, getAppName, getSubdomain } = require('./helper/cf_helper');
9
+ const { getAppFromSuggestions, getSubdomain } = require('./helper/cf_helper');
10
10
  const isBas = require('./helper/env_helper');
11
11
  const Question = require('./helper/question');
12
12
  const { CliError } = require('./helper/errors');
13
- const { httpSchema, httpsSchema, extractSubdomain, schemaRegex, localhostRegex } = require('./helper/url_helper');
13
+ const { httpSchema, httpsSchema, schemaRegex, localhostRegex } = require('./helper/url_helper');
14
14
  const { capitalize } = require('./helper/string_helper');
15
15
 
16
16
  const { ParamCollection, Persistence } = require('./params');
@@ -64,10 +64,7 @@ function getProjectFolder(params) {
64
64
  const maybeExistentPath = path.resolve(params.get('projectFolder'));
65
65
  return maybeExistentPath;
66
66
  }
67
- const existentPath = params.has('subdomain') && fs.existsSync(params.get('subdomain'))
68
- ? params.get('subdomain')
69
- : '.';
70
- return fs.realpathSync(existentPath);
67
+ return fs.realpathSync('.');
71
68
  }
72
69
 
73
70
  async function getAppUrlAndSubdomainFromSuggestions(params) {
@@ -109,7 +106,7 @@ async function getTokenBaseUrl(params, renewUrl) {
109
106
  };
110
107
 
111
108
  let existingPath;
112
- const oauthPaths = [OAUTH_PATH_STREAMLINED_MTX/*, OAUTH_PATH_OLD_MTX*/]; // The old MTX does not support HEAD requests
109
+ const oauthPaths = [OAUTH_PATH_STREAMLINED_MTX];
113
110
  for (const path of oauthPaths) {
114
111
  try {
115
112
  await axios.head(getBaseUrl(path).toString());
@@ -122,7 +119,7 @@ async function getTokenBaseUrl(params, renewUrl) {
122
119
  throw error;
123
120
  }
124
121
  }
125
- existingPath = existingPath ?? OAUTH_PATH_OLD_MTX;
122
+ existingPath = existingPath ?? OAUTH_PATH_OLD_MTX; // REVISIT can this just be included in the array?
126
123
  return getBaseUrl(existingPath).toString();
127
124
  }
128
125
 
@@ -218,12 +215,7 @@ class SettingsManager {
218
215
  }
219
216
 
220
217
  static async updateUrls(params, logout, loadedAppUrl) {
221
- async function addAppUrlAndSubdomain() {
222
- function needsSubdomain() {
223
- // Subdomain needed for token retrieval.
224
- return params.has('appUrl') && !localhostRegex.test(params.get('appUrl')) && !params.get('skipToken');
225
- }
226
-
218
+ async function addAppUrl() {
227
219
  // Prefix URL schema
228
220
  if (params.has('appUrl')) {
229
221
  const appUrl = params.get('appUrl');
@@ -237,37 +229,16 @@ class SettingsManager {
237
229
  }
238
230
  }
239
231
 
240
- if (!logout) {
232
+ if (!logout && !params.has('appUrl')) {
241
233
  // Get appUrl from CF
242
- if (!params.has('appUrl')) {
243
- DEBUG?.('App URL not given');
244
- await getAppUrlAndSubdomainFromSuggestions(params);
245
- }
246
-
247
- // Get subdomain from CF
248
- if (!params.has('subdomain') && needsSubdomain()) {
249
- DEBUG?.('Subdomain not given');
250
- const appName = await getAppName(params.get('appUrl'));
251
- if (appName) {
252
- params.set('subdomain', await getSubdomainFromCfAndNotify(appName));
253
- }
254
- }
234
+ DEBUG?.('App URL not given');
235
+ await getAppUrlAndSubdomainFromSuggestions(params);
255
236
  }
256
237
 
257
238
  // Check required param: appUrl
258
239
  if (!params.has('appUrl')) {
259
240
  throw new CliError('App URL not given. Please specify it or log in to Cloud Foundry and repeat this command.');
260
241
  }
261
-
262
- // Check required param: subdomain
263
- if (!params.has('subdomain') && needsSubdomain()) {
264
- const subdomain = extractSubdomain(params.get('appUrl'));
265
- if (!subdomain) {
266
- throw new CliError('Subdomain not given');
267
- }
268
- params.set('subdomain', subdomain);
269
- console.log('Subdomain determined from app URL:', subdomain);
270
- }
271
242
  }
272
243
  async function renewTokenUrl() {
273
244
  const renewUrl = params.get('appUrl') !== loadedAppUrl;
@@ -276,7 +247,7 @@ class SettingsManager {
276
247
  }
277
248
  }
278
249
 
279
- await addAppUrlAndSubdomain();
250
+ await addAppUrl();
280
251
 
281
252
  if (!logout && !params.get('skipToken')) {
282
253
  await renewTokenUrl();
@@ -0,0 +1,273 @@
1
+ const cp = require('child_process');
2
+ const path = require('path');
3
+ const util = require('util');
4
+
5
+ const fs = require('fs').promises;
6
+
7
+ const cds = require('../../../lib/cds');
8
+
9
+ const { build } = require('../../build');
10
+ const buildConstants = require('../../build/constants');
11
+
12
+ const hdiDeployUtil = require('./hdiDeployUtil');
13
+ const cfUtil = require('../../util/cfUtil');
14
+ const gitUtil = require('../../util/gitUtil');
15
+ const mtaUtil = require('./mtaUtil');
16
+
17
+ const { bold, info } = require('../../util/term');
18
+ const { CliError } = require('../../client/helper/errors');
19
+
20
+ const { bind } = require('../../bind');
21
+
22
+ const UTF_8 = 'utf-8';
23
+
24
+
25
+ class HanaDeployer {
26
+
27
+ constructor() {
28
+ this.execAsync = util.promisify(cp.exec);
29
+ this.spawnSync = cp.spawnSync;
30
+ }
31
+
32
+ getFromEnv(...varNames) {
33
+ const result = {}
34
+ for (const varName of varNames) {
35
+ if (process.env[varName]) {
36
+ try {
37
+ result[varName] = JSON.parse(process.env[varName]);
38
+ } catch (err) {
39
+ throw new Error(`Error parsing environment variable ${varName}`);
40
+ }
41
+ }
42
+ }
43
+ return result;
44
+ }
45
+
46
+ // arg object
47
+ // async deploy(model, serviceName, tunnelAddress, buildTaskOptions, vcapFile, undeployWhitelist, hdiOptions, bindCallback) { // NOSONAR
48
+ async deploy(options = {}) { // NOSONAR
49
+ let { model, serviceName, tunnelAddress, vcapFile, undeployWhitelist, hdiOptions } = options;
50
+
51
+ if (model) {
52
+ throw new CliError('Specifying a model for hana deployment is not supported.');
53
+ }
54
+
55
+ console.log(`${bold('Starting deploy to SAP HANA ...')}`);
56
+ if (vcapFile) {
57
+ console.log(`Using VCAP_SERVICES from file ${vcapFile} (beta feature).`);
58
+ }
59
+
60
+ const projectPath = cds.root;
61
+
62
+ const buildResults = await this._build();
63
+
64
+ let globalVcapEnv = vcapFile ? await this._loadEnvFile(vcapFile) : {};
65
+ globalVcapEnv = { ...globalVcapEnv, ...this.getFromEnv('VCAP_SERVICES', 'SERVICE_REPLACEMENTS') };
66
+ if (process.env.TARGET_CONTAINER) {
67
+ globalVcapEnv.TARGET_CONTAINER = process.env.TARGET_CONTAINER;
68
+ }
69
+
70
+ for (const buildResult of buildResults) {
71
+ let vcapEnv = globalVcapEnv;
72
+ let serviceKeyName;
73
+ const currentModelFolder = buildResult.task.dest;
74
+
75
+ if (undeployWhitelist) {
76
+ console.log('Writing undeploy.json');
77
+ await fs.write(path.join(currentModelFolder, 'undeploy.json'), JSON.stringify(undeployWhitelist, null, 2));
78
+ }
79
+
80
+ const hasVCAPEnv = Object.keys(vcapEnv).length > 0;
81
+ if (!serviceName && hasVCAPEnv) {
82
+ await fs.mkdir(currentModelFolder, { recursive: true });
83
+ } else {
84
+ const { cfServiceInstanceName, cfServiceInstanceKeyName, serviceKey } =
85
+ await this._getOrCreateCFService(projectPath, currentModelFolder, serviceName, tunnelAddress);
86
+ serviceKeyName = cfServiceInstanceKeyName;
87
+ serviceName = serviceName || cfServiceInstanceName;
88
+
89
+ vcapEnv = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
90
+
91
+ if (options.storeCredentials) {
92
+ await this._addInstanceToDefaultEnvJson([currentModelFolder, projectPath], cfServiceInstanceName, serviceKey);
93
+ }
94
+ }
95
+
96
+ hdiOptions = hdiOptions || {}
97
+ await hdiDeployUtil.deploy(currentModelFolder, vcapEnv, hdiOptions);
98
+
99
+ if (!hasVCAPEnv) {
100
+ const bindOptions = {
101
+ ...options,
102
+ to: `${serviceName}:${serviceKeyName}`,
103
+ for: options.for,
104
+ kind: buildResult.task.for
105
+ }
106
+ await bind(bindOptions);
107
+ }
108
+ }
109
+
110
+ if (options.storeCredentials) {
111
+ await gitUtil.ensureFileIsGitignored('default-env.json', projectPath);
112
+ }
113
+
114
+ console.log(`If not already done, use ${info('cds add hana')} to configure the project for SAP HANA.\n`);
115
+ console.log(`Done.`);
116
+
117
+ return { buildResults };
118
+ }
119
+
120
+
121
+ async _getOrCreateCFService(projectPath, currentModelFolder, serviceName, tunnelAddress) {
122
+ const modelName = path.basename(currentModelFolder);
123
+
124
+ // get from param
125
+ let cfServiceInstanceMta;
126
+ let cfServiceInstanceName;
127
+ if (serviceName) {
128
+ cfServiceInstanceName = serviceName;
129
+ } else {
130
+ const cfServiceDescriptor = await mtaUtil.getHanaDbModuleDescriptor(projectPath, modelName);
131
+ cfServiceInstanceName = cfServiceDescriptor.hdiServiceName;
132
+ cfServiceInstanceMta = cfServiceDescriptor.hdiService
133
+ }
134
+
135
+ console.log();
136
+ this._validateServiceInstanceName(cfServiceInstanceName);
137
+ console.log(`Using container ${bold(cfServiceInstanceName)}`);
138
+
139
+ let cfConfig = cfServiceInstanceMta && cfServiceInstanceMta.parameters && cfServiceInstanceMta.parameters.config;
140
+ const serviceInstance = await this.createHanaService(cfServiceInstanceName, cfConfig);
141
+
142
+ const cfServiceInstanceKeyName = `${cfServiceInstanceName}-key`;
143
+ let serviceKey = await cfUtil.getOrCreateServiceKey(serviceInstance, cfServiceInstanceKeyName);
144
+ this._validateServiceKey(serviceKey, cfServiceInstanceKeyName);
145
+
146
+ if (tunnelAddress) {
147
+ console.log(`Using tunnel address ${bold(tunnelAddress)} (beta feature)`);
148
+ serviceKey = this._injectTunnelAddress(serviceKey, tunnelAddress)
149
+ }
150
+
151
+ return { cfServiceInstanceName, cfServiceInstanceKeyName, serviceKey, serviceInstance }
152
+ }
153
+
154
+
155
+ async createHanaService(instanceName, cfConfig) {
156
+ // hana or hanatrial, error if neither found
157
+ try {
158
+ return await cfUtil.getOrCreateService('hana', 'hdi-shared', instanceName, cfConfig);
159
+ } catch (error) {
160
+ if (error.command && /offering .* not found/i.test(error.command.stderr)) {
161
+ console.log(`Falling back to 'hanatrial'`);
162
+ return await cfUtil.getOrCreateService('hanatrial', 'hdi-shared', instanceName, cfConfig);
163
+ }
164
+ else if (error.command && /no database/i.test(error.command.stderr)) {
165
+ console.log(`No database connected to 'hana' service. Falling back to 'hanatrial'`);
166
+ return await cfUtil.getOrCreateService('hanatrial', 'hdi-shared', instanceName, cfConfig);
167
+ }
168
+ throw error;
169
+ }
170
+ }
171
+
172
+
173
+ _validateServiceKey(serviceKey, cfServiceInstanceKey) {
174
+ if (!serviceKey) {
175
+ throw new Error(`Could not create service key ${bold(cfServiceInstanceKey)}.`);
176
+ }
177
+
178
+ const fields = ['schema', 'user', 'password', 'url'];
179
+ for (const field of fields) {
180
+ if (!serviceKey[field]) {
181
+ throw new Error(`Service key is missing mandatory field '${field}'. Make sure you are ${bold('not')} using a managed service.`);
182
+ }
183
+ }
184
+ }
185
+
186
+
187
+ async _build() {
188
+ const buildTaskOptions = {
189
+ for: buildConstants.BUILD_TASK_HANA
190
+ }
191
+
192
+ console.log(`Running build`);
193
+ const buildResults = await build(buildTaskOptions);
194
+ return buildResults;
195
+ }
196
+
197
+ async _loadEnvFile(defaultEnvFile) {
198
+ try {
199
+ const content = await fs.readFile(defaultEnvFile);
200
+ return JSON.parse(content);
201
+ } catch (err) {
202
+ throw new Error(`Error reading default env file: ${err.message}`);
203
+ }
204
+ }
205
+
206
+
207
+ async _addInstanceToDefaultEnvJson(currentFolders, serviceInstanceName, serviceKey) {
208
+ for (const currentFolder of currentFolders) {
209
+ let defaultEnvJson = {};
210
+ const defaultEnvJsonPath = path.join(currentFolder, 'default-env.json');
211
+
212
+ try {
213
+ defaultEnvJson = JSON.parse(await fs.readFile(defaultEnvJsonPath, UTF_8));
214
+ } catch (err) {
215
+ // ignore any errors
216
+ }
217
+
218
+ defaultEnvJson.VCAP_SERVICES = defaultEnvJson.VCAP_SERVICES || {};
219
+ for (const serviceKey of Object.keys(defaultEnvJson.VCAP_SERVICES)) {
220
+ defaultEnvJson.VCAP_SERVICES[serviceKey] = defaultEnvJson.VCAP_SERVICES[serviceKey].filter((service) => {
221
+ return (service.name !== serviceInstanceName);
222
+ });
223
+ }
224
+
225
+ const hanaEntry = this._getVCAPServicesEntry(serviceInstanceName, serviceKey)
226
+ Object.assign(defaultEnvJson, hanaEntry);
227
+
228
+ console.log(`Writing ${defaultEnvJsonPath}`);
229
+ await fs.mkdir(path.dirname(defaultEnvJsonPath), { recursive: true })
230
+ await fs.writeFile(defaultEnvJsonPath, JSON.stringify(defaultEnvJson, null, 2));
231
+ }
232
+ }
233
+
234
+
235
+ _getVCAPServicesEntry(serviceInstanceName, serviceKey) {
236
+ return {
237
+ VCAP_SERVICES: {
238
+ hana: [
239
+ {
240
+ name: serviceInstanceName,
241
+ tags: ['hana'],
242
+ credentials: serviceKey
243
+ }
244
+ ]
245
+ }
246
+ };
247
+ }
248
+
249
+
250
+ _validateServiceInstanceName(serviceInstanceName) {
251
+ // valid service name chars: alpha-numeric, hyphens, and underscores
252
+ if (/[^\w-_]+/g.exec(serviceInstanceName)) {
253
+ throw new Error(`Service name ${serviceInstanceName} must only contain alpha-numeric, hyphens, and underscores.`);
254
+ }
255
+ }
256
+
257
+
258
+ _injectTunnelAddress(serviceKey, tunnelAddress) {
259
+ if (!/\w+:\d+/.test(tunnelAddress)) {
260
+ throw new Error(`Invalid tunnel address '${tunnelAddress}' - must be in form 'host:port'`)
261
+ }
262
+ const [tunnelHost, tunnelPort] = tunnelAddress.split(':')
263
+ const { host, port } = serviceKey
264
+ serviceKey.host = tunnelHost
265
+ serviceKey.port = tunnelPort
266
+ serviceKey.url = serviceKey.url.replace(`${host}:${port}`, tunnelAddress)
267
+ serviceKey.hostname_in_certificate = host // make cert. verification happy, see xs2/hdideploy.js#527
268
+ serviceKey.url = serviceKey.url + (serviceKey.url.includes('?') ? '&' : '?') + 'hostNameInCertificate=' + host
269
+ return serviceKey
270
+ }
271
+ }
272
+
273
+ module.exports = new HanaDeployer();
@@ -0,0 +1,68 @@
1
+ const path = require('path');
2
+ const util = require('util');
3
+
4
+ const HDI_DEPLOY_LIB = '@sap/hdi-deploy';
5
+
6
+ class HdiDeployUtil {
7
+
8
+ async deploy(dbDir, vcapEnv = {}, options = {}) {
9
+ console.log(`Deploying to HANA from ${dbDir}`);
10
+
11
+ const hdiDeployLib = await this._loadHdiDeployLib(dbDir);
12
+
13
+ let deployerEnv = JSON.parse(JSON.stringify(process.env));
14
+ const hdiDeployOptions = deployerEnv.HDI_DEPLOY_OPTIONS ? JSON.parse(deployerEnv.HDI_DEPLOY_OPTIONS) : {};
15
+ delete hdiDeployOptions.root;
16
+
17
+ if (typeof hdiDeployLib.clean_env === 'function') {
18
+ deployerEnv = hdiDeployLib.clean_env(deployerEnv);
19
+ }
20
+
21
+ if (vcapEnv.VCAP_SERVICES) {
22
+ deployerEnv.VCAP_SERVICES = JSON.stringify(vcapEnv.VCAP_SERVICES);
23
+ }
24
+ if (vcapEnv.SERVICE_REPLACEMENTS) {
25
+ deployerEnv.SERVICE_REPLACEMENTS = JSON.stringify(vcapEnv.SERVICE_REPLACEMENTS);
26
+ }
27
+ if (vcapEnv.TARGET_CONTAINER) {
28
+ deployerEnv.TARGET_CONTAINER = vcapEnv.TARGET_CONTAINER;
29
+ }
30
+
31
+ if (options.autoUndeploy) {
32
+ console.log(`Hdi deployer automatically undeploys deleted resources using --auto-undeploy.`);
33
+ hdiDeployOptions.auto_undeploy = true;
34
+ }
35
+
36
+ if (Object.entries(hdiDeployOptions).length > 0) {
37
+ deployerEnv.HDI_DEPLOY_OPTIONS = JSON.stringify(hdiDeployOptions);
38
+ }
39
+
40
+ const asyncDeploy = util.promisify(hdiDeployLib.deploy);
41
+ const response = await asyncDeploy(dbDir, deployerEnv);
42
+ if (response?.exitCode) {
43
+ let message = `HDI deployment failed with exit code ${response.exitCode}`;
44
+ if (response.signal) {
45
+ message += `. ${response.signal}`;
46
+ }
47
+ throw new Error(message);
48
+ }
49
+ }
50
+
51
+
52
+ _loadHdiDeployLib(cwd) {
53
+ const searchPaths = [cwd, __dirname];
54
+ const libPath = require.resolve(path.join(HDI_DEPLOY_LIB, 'library'), { paths: searchPaths });
55
+ if (!libPath) {
56
+ throw new Error(`Required library '${HDI_DEPLOY_LIB}' not found in
57
+ ${searchPaths.join('\n ')}
58
+ Add it either as a devDependency using 'npm install -D ${HDI_DEPLOY_LIB}' or install it globally using 'npm install -g ${HDI_DEPLOY_LIB}'.`);
59
+ }
60
+
61
+ console.log(`Using HDI deployer from ${libPath}`)
62
+
63
+ // let any error go through and abort deploy
64
+ return require(libPath);
65
+ }
66
+ }
67
+
68
+ module.exports = new HdiDeployUtil();
@@ -0,0 +1,171 @@
1
+ const cds = require('../../cds')
2
+
3
+ const fs = require('fs').promises
4
+ const {existsSync} = require('fs')
5
+ const path = require('path')
6
+
7
+ const HDI_CONTAINER_TYPES = ['com.sap.xs.hdi-container']
8
+ const UTF_8 = 'utf-8'
9
+ const MTA_YAML = 'mta.yaml'
10
+ const DEBUG = cds.debug('cli');
11
+
12
+ async function getHanaDbModuleDescriptor(projectPath, moduleName) {
13
+ // mta might be null if mta.yaml does not exist
14
+ const mta = await _getMta(projectPath)
15
+ const projectInfo = await _getProjectInfo(projectPath, mta)
16
+ const hdiService = _findHdiResource(mta, moduleName)
17
+ const appName = _getApplicationName(mta, moduleName, 'hdb')
18
+
19
+ return {
20
+ project: projectInfo,
21
+ appName: appName ? appName : `${projectInfo.name}-${moduleName}-deployer`,
22
+ hdiServiceName: hdiService ? hdiService.name : `${projectInfo.name}-${moduleName}`,
23
+ hdiService: hdiService
24
+ }
25
+ }
26
+
27
+ async function getServiceModuleDescriptor(projectPath, moduleName, moduleType) {
28
+ // mta might be null if mta.yaml does not exist
29
+ const mta = await _getMta(projectPath)
30
+ const projectInfo = await _getProjectInfo(projectPath, mta)
31
+ const appName = _getApplicationName(mta, moduleName, moduleType)
32
+
33
+ return {
34
+ project: projectInfo,
35
+ appName: appName ? appName : `${projectInfo.name}-${moduleName}`
36
+ }
37
+ }
38
+
39
+ async function _getProjectInfo(projectPath, mta) {
40
+ const details = {
41
+ }
42
+ // 1. use mta data
43
+ if (mta) {
44
+ details.id = mta.ID
45
+ details.description = mta.description
46
+ details.version = mta.version
47
+ if (mta.ID) {
48
+ details.name = mta.ID.replace(/\./g, '-')
49
+ }
50
+ }
51
+ // 2. use package.json data
52
+ const packageJsonPath = path.join(projectPath, 'package.json')
53
+ if (existsSync(packageJsonPath)) {
54
+ try {
55
+ const packageJson = JSON.parse((await fs.readFile(packageJsonPath, 'utf-8')).toString())
56
+ if (!details.description && packageJson.description) {
57
+ details.description = packageJson.description
58
+ }
59
+ if (!details.version && packageJson.version) {
60
+ details.version = packageJson.version
61
+ }
62
+ if (packageJson.name) {
63
+ let segments = packageJson.name.trim().split('/')
64
+ // scope as namespace
65
+ if (segments[0].startsWith('@')) {
66
+ segments[0] = segments[0].replace('@', '')
67
+ }
68
+ segments = segments.map(segment => segment.startsWith('@') ? encodeURIComponent(segment.replace('@', '')) : encodeURIComponent(segment))
69
+ if (!details.name) {
70
+ details.name = segments[segments.length - 1]
71
+ }
72
+ if (!details.id) {
73
+ details.id = segments.join('.')
74
+ }
75
+ if (!details.description) {
76
+ details.description = segments[segments.length - 1]
77
+ }
78
+ }
79
+ } catch (e) {
80
+ console.error(`Failed to load ${packageJsonPath} - skip application data`, e)
81
+ }
82
+ }
83
+ // 3. use project name and static default values
84
+ const projectName = path.basename(projectPath)
85
+ if (!details.name) {
86
+ details.name = projectName
87
+ }
88
+ if (!details.description) {
89
+ details.description = projectName
90
+ }
91
+ if (!details.id) {
92
+ details.id = projectName
93
+ }
94
+ if (!details.version) {
95
+ details.version = '1.0.0'
96
+ }
97
+ return details
98
+ }
99
+
100
+ async function _getMta(projectPath) {
101
+ // yaml.parse oesn't like null
102
+ const mtaFilePath = path.join(projectPath, MTA_YAML)
103
+
104
+ const existsMtaYaml = existsSync(mtaFilePath)
105
+ if (!existsMtaYaml) {
106
+ DEBUG?.('mta.yaml not existing')
107
+ return null
108
+ }
109
+
110
+ const yamlStr = await fs.readFile(mtaFilePath, UTF_8)
111
+
112
+ // yaml returns null if string couldn't be parsed, e.g. empty string
113
+ return cds.parse.yaml(yamlStr)
114
+ }
115
+
116
+ function _getApplicationName(mta, moduleName, moduleType) {
117
+ const module = _findModule(mta, moduleName, moduleType)
118
+ if (module) {
119
+ const appName = module.parameters && module.parameters['app-name']
120
+ return appName ? appName : module.name
121
+ }
122
+ return null
123
+ }
124
+
125
+ function _findModules(mta, moduleType) {
126
+ let modules = []
127
+ if (mta && Array.isArray(mta.modules)) {
128
+ modules = mta.modules.filter(module => module.type === moduleType)
129
+ }
130
+ return modules
131
+ }
132
+
133
+ function _findModule(mta, moduleName, moduleType) {
134
+ const modules = _findModules(mta, moduleType)
135
+ if (modules.length === 1) {
136
+ return modules[0]
137
+ } else if (modules.length > 1) {
138
+ return modules.find(module => typeof module.path === 'string' && module.path.includes(moduleName))
139
+ }
140
+ return null
141
+ }
142
+
143
+ function _findHdiResource(mta, moduleName) {
144
+ if (mta && Array.isArray(mta.resources)) {
145
+ const hdiResources = mta.resources.filter(resource => HDI_CONTAINER_TYPES.includes(resource.type))
146
+
147
+ if (hdiResources.length > 0) {
148
+ if (Array.isArray(mta.modules)) {
149
+ const module = _findModule(mta, moduleName, 'hdb')
150
+ if (module && Array.isArray(module.requires)) {
151
+ return hdiResources.find(hdiResource =>
152
+ module.requires.find(req =>
153
+ hdiResource.name === req.name
154
+ )
155
+ )
156
+ }
157
+ }
158
+
159
+ console.warn(`No matching hdi resource found for ${moduleName}. Using ${hdiResources[0].name}.`)
160
+ return hdiResources[0]
161
+ }
162
+ }
163
+
164
+ return null
165
+ }
166
+
167
+ module.exports = {
168
+ getHanaDbModuleDescriptor,
169
+ getServiceModuleDescriptor,
170
+ MTA_YAML
171
+ }
@@ -1,4 +1,3 @@
1
-
2
1
  const ParserContext = require("./parserContext");
3
2
  const cdsDkVersion = require("../../../package.json").version;
4
3
 
@@ -29,4 +28,6 @@ function convertToCSN(context) {
29
28
  return csn;
30
29
  }
31
30
 
32
- module.exports = { importAsyncAPI }
31
+ module.exports = {
32
+ importAsyncAPI
33
+ }
@@ -1,9 +1,12 @@
1
1
  const { importAsyncAPI } = require("./importAsyncAPI");
2
2
  const { validateAsyncAPI } = require('./utils');
3
3
 
4
- /* The starting function for conversion of AsyncAPI document to csn*/
4
+ /**
5
+ * The starting function for conversion of AsyncAPI document to csn
6
+ * @returns AsyncAPI document
7
+ */
5
8
  function asyncapi2csn(source) {
6
- //validate asyncapi document
9
+ // validate asyncapi document
7
10
  const content = JSON.parse(source);
8
11
  validateAsyncAPI(content);
9
12