@sap/cds-dk 7.8.2 → 7.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/bin/add.js +28 -7
  2. package/bin/bind.js +64 -21
  3. package/bin/build.js +35 -3
  4. package/bin/cds.js +31 -13
  5. package/bin/compile/index.js +44 -3
  6. package/bin/completion/completionFs.js +61 -0
  7. package/bin/completion/index.js +212 -0
  8. package/bin/completion/scripts/cds-bash.sh +17 -0
  9. package/bin/completion/scripts/cds-ps.ps1 +18 -0
  10. package/bin/completion/scripts/cds-zsh.sh +12 -0
  11. package/bin/help.js +8 -2
  12. package/bin/init.js +34 -4
  13. package/bin/subscribe.js +5 -1
  14. package/bin/watch.js +4 -2
  15. package/lib/bind/KubernetesApiAccessor.js +3 -2
  16. package/lib/bind/bindingResolver.js +3 -2
  17. package/lib/bind/bindingUtil.js +10 -8
  18. package/lib/bind/cfKeyProvider.js +58 -153
  19. package/lib/bind/index.js +46 -36
  20. package/lib/bind/kubernetesCredentialsProvider.js +6 -5
  21. package/lib/bind/userCdsrcJsonStorageProvider.js +25 -3
  22. package/lib/build/buildTaskEngine.js +1 -1
  23. package/lib/build/provider/hana/index.js +1 -1
  24. package/lib/build/provider/hana/template/undeploy.json +3 -1
  25. package/lib/build/provider/nodejs/index.js +1 -1
  26. package/lib/compile/openapi/index.js +5 -0
  27. package/lib/deploy/to-hana/hana.js +25 -7
  28. package/lib/env/schemas/cds-rc.js +29 -2
  29. package/lib/import/odata/v4parser.js +78 -25
  30. package/lib/init/constants.js +2 -1
  31. package/lib/init/index.js +18 -13
  32. package/lib/init/merge.js +1 -0
  33. package/lib/init/mvn.js +8 -47
  34. package/lib/init/plugin.js +0 -4
  35. package/lib/init/projectReader.js +17 -10
  36. package/lib/init/template/approuter/files/values.yaml.hbs +0 -12
  37. package/lib/init/template/completion/completionSetup.js +184 -0
  38. package/lib/init/template/completion/index.js +22 -0
  39. package/lib/init/template/data/as-csv.js +25 -17
  40. package/lib/init/template/data/as-json.js +115 -30
  41. package/lib/init/template/data/index.js +28 -15
  42. package/lib/init/template/hana/files/undeploy.json +2 -1
  43. package/lib/init/template/helm/files/chart/values.schema.json +1 -1
  44. package/lib/init/template/helm/files/content-deployment/Chart.yaml +0 -2
  45. package/lib/init/template/helm/files/content-deployment/values.schema.json +0 -1
  46. package/lib/init/template/helm/files/service-instance/Chart.yaml +0 -2
  47. package/lib/init/template/helm/files/service-instance/values.schema.json +0 -1
  48. package/lib/init/template/helm/files/web-application/Chart.yaml +0 -2
  49. package/lib/init/template/helm/files/web-application/values.schema.json +0 -1
  50. package/lib/init/template/helm-unified-runtime/files/values.schema.json +1 -1
  51. package/lib/init/template/http/index.js +150 -104
  52. package/lib/init/template/kafka/index.js +6 -0
  53. package/lib/init/template/lint/index.js +12 -13
  54. package/lib/init/template/mta/files/mta.yaml.hbs +1 -1
  55. package/lib/init/template/multitenancy/files/values.yaml.hbs +0 -5
  56. package/lib/init/template/multitenancy/index.js +13 -5
  57. package/lib/init/template/nodejs/files/.vscode/settings.json +11 -0
  58. package/lib/init/template/nodejs/files/eslint.config.js +13 -0
  59. package/lib/init/template/nodejs/files/package.json.hbs +3 -1
  60. package/lib/init/template/sample/index.js +18 -20
  61. package/lib/init/template/tiny-sample/index.js +1 -2
  62. package/lib/init/template/typer/index.js +2 -2
  63. package/lib/lint/Linter.js +133 -55
  64. package/lib/lint/checks.js +5 -6
  65. package/lib/lint/io.js +119 -10
  66. package/lib/mtx/auth_manager.js +5 -5
  67. package/lib/mtx/params.js +4 -2
  68. package/lib/mtx/settings_manager.js +1 -1
  69. package/lib/mtx/tenant_action.js +12 -2
  70. package/lib/mtx/util/logging.js +10 -4
  71. package/lib/util/axios.js +2 -1
  72. package/lib/util/cf.js +32 -37
  73. package/lib/util/git.js +1 -1
  74. package/lib/util/term.js +1 -1
  75. package/{bin → lib/watch}/watched.js +2 -2
  76. package/npm-shrinkwrap.json +34 -34
  77. package/package.json +2 -2
  78. package/lib/init/template/nodejs/files/.eslintrc +0 -26
@@ -2,109 +2,102 @@ const cds = require('../cds');
2
2
  const DEBUG = cds.debug('cli')
3
3
 
4
4
  const cfUtil = require('../util/cf');
5
-
6
- const axios = require('axios');
7
- require('../util/axios').pruneErrors();
8
- const { URL } = require('url');
9
- const { bold, highlight } = require('../util/term');
5
+ const { bold } = require('../util/term');
6
+ const { mergeCredentials } = require('./index');
10
7
 
11
8
  module.exports = new class CFKeyProvider {
12
- async getCfSpaceInfo() {
13
- DEBUG?.('getting space info');
14
-
15
- const target = await cfUtil.getCfTarget();
16
- const authorization = await cfUtil.getCfAuthorization();
17
- const targetWithAuth = Object.assign({}, target, { authorization });
18
-
19
-
20
- const { org, space } = target;
21
- const orgs = await BatchRequest.req(targetWithAuth, `/v3/organizations?names=${encodeURIComponent(encodeURIComponent(org))}`);
22
- if (!(orgs && orgs.resources.length === 1)) {
23
- throw `CF org ${org} not found!`;
9
+ async init() {
10
+ if (!this.target) {
11
+ this.target = await cfUtil.getCfSpaceInfo();
24
12
  }
13
+ }
25
14
 
26
- const orgGuid = orgs.resources[0].guid;
27
- const spaces = await BatchRequest.req(targetWithAuth, `/v3/spaces?names=${encodeURIComponent(encodeURIComponent(space))}&organization_guids=${encodeURIComponent(encodeURIComponent(orgGuid))}`)
28
- if (!(spaces && spaces.resources.length === 1)) {
29
- throw `CF space ${space} not found in org ${org}!`;
30
- }
15
+ async getOrg(orgName) {
16
+ const result = await cfUtil.cfRequest('/v3/organizations', { names: orgName });
17
+ return result?.resources?.[0];
18
+ }
31
19
 
32
- const spaceGuid = spaces.resources[0].guid;
20
+ async getSpace(orgGuid, spaceName) {
21
+ const result = await cfUtil.cfRequest('/v3/spaces', { names: spaceName, organization_guids: orgGuid });
22
+ return result?.resources?.find(r => r.relationships.organization.data.guid === orgGuid);
23
+ }
33
24
 
34
- return Object.assign({}, target, { orgGuid, spaceGuid, authorization });
25
+ async getInstance(spaceGuid, instanceName) {
26
+ const result = await cfUtil.cfRequest('/v3/service_instances', { names: instanceName, space_guids: spaceGuid });
27
+ return result?.resources?.find(r => !!r.relationships.space.data.guid);
35
28
  }
36
29
 
37
- async init() {
38
- const target = this.target = await this.getCfSpaceInfo();
39
- this.getOrg = BatchRequest.create({ target, url: '/v3/organizations' });
40
- this.getSpace = BatchRequest.create({
41
- target, url: '/v3/spaces', extractors: {
42
- organization_guids: resource => resource.relationships.organization.data.guid
43
- }
44
- });
45
- this.getInstance = BatchRequest.create({
46
- target, url: '/v3/service_instances', extractors: {
47
- space_guids: resource => resource.relationships.space.data.guid
48
- }
49
- });
50
- this.getKey = BatchRequest.create({
51
- target, url: '/v3/service_credential_bindings', extractors: {
52
- service_instance_guids: resource => resource.relationships.service_instance.data.guid
53
- }
54
- });
55
- this.getPlans = BatchRequest.create({ target, url: '/v3/service_plans' });
56
- this.getOfferings = BatchRequest.create({ target, url: '/v3/service_offerings' });
30
+ async getPlan(planGuid) {
31
+ const result = await cfUtil.cfRequest('/v3/service_plans', { guids: planGuid });
32
+ return result?.resources?.[0];
33
+ }
34
+
35
+ async getOffering(offeringGuid) {
36
+ const result = await cfUtil.cfRequest('/v3/service_offerings', { guids: offeringGuid });
37
+ return result?.resources?.[0];
57
38
  }
58
39
 
59
40
  async resolve(name, binding) {
60
- if (!this.initPromise) {
61
- this.initPromise = this.init();
62
- }
63
- await this.initPromise;
41
+ await this.init();
64
42
  this._checkApiEndpoint(name, binding)
65
43
 
66
44
  let { instance, org, space } = binding;
67
- let spaceGuid;
45
+ let spaceGuid, orgGuid;
68
46
  if (org && space) {
69
47
  // org and space can be overwritten by currently logged in org and space
70
- const orgObj = await this.getOrg({ names: org });
71
- const spaceObj = await this.getSpace({ names: space, organization_guids: orgObj.guid });
48
+ const orgObj = await this.getOrg(org);
49
+ const spaceObj = await this.getSpace(orgObj.guid, space);
72
50
  spaceGuid = spaceObj.guid;
51
+ orgGuid = orgObj.guid;
73
52
  } else {
74
53
  org = this.target.org;
75
54
  space = this.target.space;
76
55
  spaceGuid = this.target.spaceGuid;
56
+ orgGuid = this.target.orgGuid;
77
57
  }
78
58
 
79
59
  let instanceObj;
80
60
  try {
81
- instanceObj = await this.getInstance({ names: instance, space_guids: spaceGuid });
61
+ instanceObj = await this.getInstance(spaceGuid, instance);
62
+ const instanceSpaceGuid = instanceObj.relationships.space.data.guid;
63
+ if (instanceSpaceGuid !== spaceGuid) {
64
+ // service instance is shared from another space, service key must be created in that space
65
+ const sharedFromSpaceObj = await cfUtil.cfRequest('/v3/spaces/' + encodeURIComponent(instanceSpaceGuid));
66
+ space = sharedFromSpaceObj.name;
67
+ spaceGuid = sharedFromSpaceObj.guid;
68
+ const instanceOrgGuid = sharedFromSpaceObj.relationships.organization.data.guid;
69
+ if (instanceOrgGuid !== orgGuid) {
70
+ const sharedFromOrgObj = await cfUtil.cfRequest('/v3/organizations/' + encodeURIComponent(instanceOrgGuid));
71
+ org = sharedFromOrgObj.name;
72
+ orgGuid = sharedFromOrgObj.guid;
73
+ }
74
+ }
82
75
  } catch (error) {
83
- throw `Service instance "${instance}" not found.`;
76
+ throw `Service instance ${bold(instance)} not found.`;
84
77
  }
85
78
  switch (instanceObj.type) {
86
79
  case 'managed':
87
- return this._resolveManagedService(instanceObj, instance, binding.key || `${instance}-key`, org, space);
80
+ return this._resolveManagedService(instanceObj, instance, binding.key || `${instance}-key`, org, space, binding.credentials);
88
81
  case 'user-provided':
89
- return this._resolveUserProvidedService(instanceObj, instance, org, space);
82
+ return this._resolveUserProvidedService(instanceObj, instance, org, space, binding.credentials);
90
83
  default:
91
- throw `Service type ${instanceObj.type} not supported`;
84
+ throw `Service type ${bold(instanceObj.type)} not supported`;
92
85
  }
93
86
  }
94
87
 
95
- async _resolveManagedService(instanceObj, instance, key, org, space) {
88
+ async _resolveManagedService(instanceObj, instance, key, org, space, customCredentials) {
96
89
  const create = (cds.cli.command === 'bind' || cds.cli.command === 'deploy') && !cds.cli.options['no-create-service-key']
97
90
  const silent = (cds.cli.options.json || cds.cli.command === 'env') && !DEBUG
98
91
  const credentials = await cfUtil.getOrCreateServiceKey(instanceObj, key, {}, { create, silent });
99
92
 
100
93
  if (!credentials) {
101
- let message = `No service key ${highlight(key)} found for service instance ${highlight(instance)}.\n\n`;
94
+ let message = `No service key ${bold(key)} found for service instance ${bold(instance)}.\n\n`;
102
95
  message += `Use ${bold(`cf create-service-key ${instanceObj.name} ${key} [-c ...]`)} to create the required service key.`;
103
96
  throw message;
104
97
  }
105
98
 
106
- const planObj = await this.getPlans({ guids: instanceObj.relationships.service_plan.data.guid });
107
- const offeringObj = await this.getOfferings({ guids: planObj.relationships.service_offering.data.guid });
99
+ const planObj = await this.getPlan(instanceObj.relationships.service_plan.data.guid);
100
+ const offeringObj = await this.getOffering(planObj.relationships.service_offering.data.guid);
108
101
  const resolvedBinding = {
109
102
  binding: {
110
103
  type: "cf",
@@ -120,14 +113,14 @@ module.exports = new class CFKeyProvider {
120
113
  },
121
114
  resolved: true,
122
115
  },
123
- credentials
116
+ credentials: mergeCredentials(credentials, customCredentials)
124
117
  };
125
118
 
126
119
  return resolvedBinding;
127
120
  }
128
121
 
129
- async _resolveUserProvidedService(instanceObj, instance, org, space) {
130
- const credentialsObj = await BatchRequest.req(this.target, `/v3/service_instances/${encodeURIComponent(instanceObj.guid)}/credentials`);
122
+ async _resolveUserProvidedService(instanceObj, instance, org, space, customCredentials) {
123
+ const credentials = await cfUtil.cfRequest(`/v3/service_instances/${encodeURIComponent(instanceObj.guid)}/credentials`);
131
124
  const resolvedBinding = {
132
125
  binding: {
133
126
  type: "cf",
@@ -141,7 +134,7 @@ module.exports = new class CFKeyProvider {
141
134
  },
142
135
  resolved: true,
143
136
  },
144
- credentials: credentialsObj
137
+ credentials: mergeCredentials(credentials, customCredentials)
145
138
  };
146
139
  return resolvedBinding;
147
140
  }
@@ -152,97 +145,9 @@ module.exports = new class CFKeyProvider {
152
145
  // if no api endpoint is configured the currently logged in endpoint will be used
153
146
  if (apiEndpoint && apiEndpoint !== this.target.apiEndpoint) {
154
147
  name = `${name || instance}:${key || instance + '-key'}`;
155
- let message = `Current Cloud Foundry API endpoint ${highlight(this.target.apiEndpoint)} differs from API endpoint ${highlight(apiEndpoint)} for service binding ${highlight(name)}.\n\n`;
148
+ let message = `Current Cloud Foundry API endpoint ${bold(this.target.apiEndpoint)} differs from API endpoint ${bold(apiEndpoint)} for service binding ${bold(name)}.\n\n`;
156
149
  message += `Use ${bold(`cf login -a ${apiEndpoint}`)} to log in to the target API endpoint.`;
157
150
  throw message;
158
151
  }
159
152
  }
160
153
  }
161
-
162
- class BatchRequest {
163
- constructor({ target, url, extractors = {} }) {
164
- this.target = target;
165
- this.url = url;
166
- this.filters = [];
167
- this.timeout = undefined;
168
- this.extractors = Object.assign({ names: resource => resource.name, guids: resource => resource.guid }, extractors);
169
- }
170
-
171
- static create(options) {
172
- const obj = new BatchRequest(options);
173
- return (...args) => obj.request(...args);
174
- }
175
-
176
- async request(filter) {
177
- let resolve;
178
- let reject;
179
- const promise = new Promise((_resolve, _reject) => {
180
- resolve = _resolve;
181
- reject = _reject;
182
- });
183
- this.filters.push({ filter, resolve, reject });
184
-
185
- if (this.timeout) clearTimeout(this.timeout);
186
- this.timeout = setTimeout(() => this.executeRequest(), 0);
187
-
188
- return promise;
189
- }
190
-
191
- async executeRequest() {
192
- const filters = this.filters;
193
- this.filters = [];
194
- const filterParams = {};
195
- for (const filter of filters) {
196
- for (const name of Object.keys(filter.filter)) {
197
- if (!filterParams[name]) filterParams[name] = [];
198
- filterParams[name].push(filter.filter[name]);
199
- }
200
- }
201
-
202
- const params = Object.keys(filterParams).map(
203
- name => `${name}=${encodeURIComponent(filterParams[name].map(encodeURIComponent).join(','))}`
204
- ).join("&");
205
-
206
- const url = this.url + (this.url.match(/\?/) ? '&' : '?') + params;
207
-
208
- let data;
209
- try {
210
- data = await this.httpRequest(url);
211
- } catch (error) {
212
- for (const filter of filters) {
213
- filter.reject(error);
214
- }
215
- return;
216
- }
217
- const fmt = filter => Object.keys(filter).map(key => `${key}: ${filter[key]}`).join(', ');
218
- const resources = data.resources;
219
- for (const filter of filters) {
220
- let filteredResources = resources;
221
- for (const name of Object.keys(filter.filter)) {
222
- filteredResources = filteredResources.filter(resource => this.extractors[name](resource) === filter.filter[name]);
223
- }
224
- if (filteredResources.length === 1) {
225
- filter.resolve(filteredResources[0]);
226
- } else if (filteredResources.length === 0) {
227
- filter.reject(`Couldn't find resource for url "${this.url}" with filter "${fmt(filter.filter)}"`);
228
- } else {
229
- filter.reject(`Found more than one resources for url "${this.url}" with filter "${fmt(filter.filter)}"`);
230
- }
231
- }
232
- }
233
-
234
- async httpRequest(urlPath) {
235
- return BatchRequest.req(this.target, urlPath);
236
- }
237
-
238
- static async req(target, urlPath) {
239
- const url = new URL(urlPath, target.apiEndpoint);
240
- DEBUG?.(`_req: ${url.toString()}`);
241
- const result = await axios.get(url.toString(), {
242
- headers: {
243
- authorization: target.authorization
244
- }
245
- });
246
- return result.data;
247
- }
248
- }
package/lib/bind/index.js CHANGED
@@ -2,10 +2,10 @@ const cds = require('../cds');
2
2
 
3
3
  const BindingResolver = require('./bindingResolver');
4
4
  const { determineKinds, getRequiresNameFromKind, checkIsJavaProject, platformText, bindingText, logger } = require('./bindingUtil');
5
- const { highlight, bold, info } = require('../util/term');
5
+ const { bold } = require('../util/term');
6
6
 
7
7
 
8
- module.exports = { bind, parseProfile }
8
+ module.exports = { bind, storeBindings, parseProfile, mergeCredentials }
9
9
 
10
10
  /**
11
11
  * Executes cds build in the directory defined by cds.root.
@@ -13,11 +13,8 @@ module.exports = { bind, parseProfile }
13
13
  * @param {object} options - command options as defined by build command.
14
14
  */
15
15
  async function bind(options) {
16
-
17
- options.for = options.for || 'hybrid';
18
-
19
16
  let on;
20
- switch(options.on) {
17
+ switch (options.on) {
21
18
  case 'k8s':
22
19
  case 'kubernetes':
23
20
  on = 'k8s';
@@ -33,36 +30,26 @@ async function bind(options) {
33
30
  }
34
31
 
35
32
  const onText = { 'cf': 'Cloud Foundry', 'k8s': 'Kubernetes' }[on];
36
- if (!options['to-app-services']) logger.log(`Retrieving data from ${onText}...`);
33
+ if (!options['to-app-services']) logger.log(`Retrieving data from ${bold(onText)}...`);
37
34
  let resolvedServices
38
35
 
39
36
  const targets = options.to.split(/,/g);
40
- try {
41
- resolvedServices = await Promise.all(targets.map(async target => {
42
- let binding;
43
- if (on === 'k8s') {
44
- binding = { type: on, name: target };
45
- } else if (on === 'cf') {
46
- const [instance, key] = target.split(/:/);
47
- binding = { type: on, instance, key };
48
- } else {
49
- throw new Error('Internal Error');
50
- }
51
- const resolvedService = await BindingResolver.resolveBinding(undefined, binding);
52
- resolvedService._target = target;
53
- delete resolvedService.credentials;
54
- resolvedService.binding.resolved = false;
55
- return resolvedService;
56
- }));
57
- } catch (error) {
58
- if (error.name === 'AxiosError') {
59
- const errors = error.response?.data?.errors ?? []
60
- const responseInfo = errors.map(e => `${e.title}: ${e.detail} (${e.code})`).join(' \n')
61
- const requestInfo = error.request?.path
62
- throw new Error(`${error.toString()}\nResponse: ${responseInfo}\nRequest:${requestInfo}}`)
37
+ resolvedServices = await Promise.all(targets.map(async target => {
38
+ let binding;
39
+ if (on === 'k8s') {
40
+ binding = { type: on, name: target };
41
+ } else if (on === 'cf') {
42
+ const [instance, key] = target.split(/:/);
43
+ binding = { type: on, instance, key };
44
+ } else {
45
+ throw new Error('Internal Error');
63
46
  }
64
- throw error
65
- }
47
+ const resolvedService = await BindingResolver.resolveBinding(undefined, binding);
48
+ resolvedService._target = target;
49
+ delete resolvedService.credentials;
50
+ resolvedService.binding.resolved = false;
51
+ return resolvedService;
52
+ }));
66
53
 
67
54
  if (options.kind) {
68
55
  resolvedServices.forEach(service => service.kind = options.kind);
@@ -80,12 +67,16 @@ async function bind(options) {
80
67
  let { kind, name, binding } = resolvedService;
81
68
  name = name ?? `custom-service:${binding.instance || binding.secret}`
82
69
  resolvedService.name = resolvedService.name ?? name
83
- const kindText = kind ? ` with kind ${highlight(kind)}` : ''
84
- const nameText = highlight(name)
70
+ const kindText = kind ? ` with kind ${bold(kind)}` : ''
71
+ const nameText = bold(name)
85
72
 
86
- logger.log(`Binding ${nameText} to ${platformText(binding)} ${bindingText(binding, highlight)}${kindText}`);
73
+ logger.log(`Binding ${nameText} to ${platformText(binding)} ${bindingText(binding)}${kindText}`);
87
74
  }
88
75
 
76
+ return resolvedServices;
77
+ }
78
+
79
+ async function storeBindings(resolvedServices, options) {
89
80
  // prepare for different providers
90
81
  const userCdsrcJsonStorageProvider = require('./userCdsrcJsonStorageProvider');
91
82
 
@@ -95,7 +86,7 @@ async function bind(options) {
95
86
 
96
87
  const isJavaProject = await checkIsJavaProject(cds.root);
97
88
  if (!cds.cli.options['to-app-services']) {
98
- logger.log(`${info('TIP:')} Run with cloud bindings: ${bold(runCmd(options.for, isJavaProject))}`);
89
+ logger.log(`\nTo run with cloud bindings use ${bold(runCmd(options.for, isJavaProject))}`);
99
90
  }
100
91
  }
101
92
 
@@ -115,3 +106,22 @@ function parseProfile(profile) {
115
106
  else if (profile === '-') return 'development';
116
107
  else return profile;
117
108
  }
109
+
110
+ function mergeCredentials(credentials, customCredentials) {
111
+ if (!customCredentials) return credentials;
112
+
113
+ for (const key in customCredentials) {
114
+ if (typeof customCredentials[key] === 'object') {
115
+ if (credentials[key] && typeof credentials[key] !== 'object') {
116
+ throw `Cannot merge credentials: '${key}' is not an object`;
117
+ }
118
+ if (!credentials[key]) {
119
+ credentials[key] = {};
120
+ }
121
+ credentials[key] = this._mergeCredentials(credentials[key], customCredentials[key]);
122
+ } else {
123
+ credentials[key] = customCredentials[key];
124
+ }
125
+ }
126
+ return credentials;
127
+ }
@@ -1,4 +1,5 @@
1
1
  const KubernetesApiAccessor = require('./KubernetesApiAccessor');
2
+ const { mergeCredentials } = require('./index');
2
3
 
3
4
  class KubernetesCredentialsProvider {
4
5
  async init() {
@@ -12,14 +13,14 @@ class KubernetesCredentialsProvider {
12
13
  await this.initPromise;
13
14
 
14
15
 
15
- const {context, namespace, cluster} = await this.api.context({cluster: binding.cluster, namespace: binding.namespace});
16
- let {name: bindingName, secret} = binding;
16
+ const { context, namespace, cluster } = await this.api.context({ cluster: binding.cluster, namespace: binding.namespace });
17
+ let { name: bindingName, secret, credentials: customCredentials } = binding;
17
18
 
18
19
  let instance;
19
20
  let secretOrBindingResource = 'Secret';
20
21
 
21
22
  if (bindingName) {
22
- const bindingResource = await this.api.get({context, namespace, kind: 'servicebindings', name: bindingName});
23
+ const bindingResource = await this.api.get({ context, namespace, kind: 'servicebindings', name: bindingName });
23
24
  if (bindingResource === null) {
24
25
  // Fallback to secret instead of service binding;
25
26
  secret = bindingName;
@@ -35,7 +36,7 @@ class KubernetesCredentialsProvider {
35
36
  }
36
37
  }
37
38
 
38
- const secretResource = await this.api.get({context, namespace, kind: 'secrets', name: secret});
39
+ const secretResource = await this.api.get({ context, namespace, kind: 'secrets', name: secret });
39
40
  if (secretResource === null) throw new Error(`${secretOrBindingResource} '${secret}' not found.`);
40
41
 
41
42
  const serviceBinding = this._readBinding(name, secretResource);
@@ -53,7 +54,7 @@ class KubernetesCredentialsProvider {
53
54
  resolved: true,
54
55
  vcap
55
56
  },
56
- credentials
57
+ credentials: mergeCredentials(credentials, customCredentials)
57
58
  };
58
59
 
59
60
  return resolvedBinding;
@@ -3,7 +3,7 @@ const path = require('path');
3
3
  const readline = require('readline');
4
4
 
5
5
  const fs = require('fs').promises;
6
- const { highlight } = require('../util/term');
6
+ const { bold } = require('../util/term');
7
7
  const { logger } = require('./bindingUtil');
8
8
 
9
9
  const gitUtil = require('../util/git');
@@ -38,7 +38,6 @@ class UserCdsrcJsonStorageProvider {
38
38
  }
39
39
 
40
40
  jsonPath = jsonPath.replace(/~/g, os.homedir());
41
- // const stat = await .stat(jsonPath);
42
41
  if (path.extname(jsonPath) !== '.json') {
43
42
  jsonPath = path.join(jsonPath, '.cdsrc.json');
44
43
  }
@@ -63,12 +62,14 @@ class UserCdsrcJsonStorageProvider {
63
62
  for (const service of services) {
64
63
  const { name, kind, binding, credentials } = service;
65
64
  const requireService = section[name] = section[name] || {};
65
+ // store optional custom credentials
66
+ binding.credentials = this._getServiceCredentials(binding, services, options);
66
67
 
67
68
  Object.assign(requireService, { binding: { ...binding, vcap: undefined }, credentials, kind, vcap: { name } });
68
69
  }
69
70
 
70
71
  await this.writeJsonFile(jsonPath, jsonObject);
71
- logger.log(`Saving bindings to ${highlight(jsonPath)} in profile ${highlight(options.for)}`);
72
+ logger.log(`Saving bindings to ${bold(jsonPath)} in profile ${bold(options.for)}`);
72
73
  }
73
74
 
74
75
  async readJsonSafely(filePath) {
@@ -84,6 +85,27 @@ class UserCdsrcJsonStorageProvider {
84
85
  await fs.writeFile(filePath, JSON.stringify(content, null, 2));
85
86
  await fs.chmod(filePath, 0o600); // -rw-------
86
87
  }
88
+
89
+ _getServiceCredentials(binding, services, options) {
90
+ const { to, 'to-app-services': app, credentials } = options;
91
+ if (!credentials) {
92
+ return
93
+ }
94
+ // toService has format <service-name>[:<key>]
95
+ const toServices = to ? to.split(',') : services.map(s => s.binding.instance);
96
+ const toService = toServices.find(toService => binding.type === 'cf' ?
97
+ toService === binding.instance || toService === binding.instance + ':' + binding.key :
98
+ toService === binding.secret);
99
+ if (!toService) {
100
+ // must never happen
101
+ throw new Error(`Internal error: Service ${binding.instance}:${binding.key} not found`);
102
+ }
103
+ // app based binding always requires service name as key in credentials
104
+ if (!app && toServices.length === 1) {
105
+ return credentials[toService] || credentials;
106
+ }
107
+ return credentials[toService];
108
+ }
87
109
  }
88
110
 
89
111
  module.exports = new UserCdsrcJsonStorageProvider();
@@ -227,7 +227,7 @@ class BuildTaskEngine {
227
227
  files.length = LOG_LIMIT
228
228
  files.push(`... ${length - files.length} more. Run with DEBUG=build to show all files.`)
229
229
  }
230
- console.log(`done > wrote output to:\n ${term.secondary(files.join("\n "))}\n`)
230
+ console.log(`done > wrote output to:\n ${term.dim(files.join("\n "))}\n`)
231
231
  }
232
232
  }
233
233
 
@@ -398,7 +398,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
398
398
  * Create .hdiconfig file in <dest>src/gen folder of db module.
399
399
  */
400
400
  async _writeHdiConfig(compileHdiPlugins) {
401
- const DEFAULT_HDI_PLUGINS = [FILE_EXT_CSV, FILE_EXT_HDBTABLEDATA, FILE_EXT_HDBTABLE, ".hdbview", ".hdbindex", ".hdbconstraint"]
401
+ const DEFAULT_HDI_PLUGINS = [FILE_EXT_CSV, FILE_EXT_HDBTABLEDATA, FILE_EXT_HDBTABLE, ".hdbview", ".hdbindex", ".hdbconstraint", ".hdbcalculationview"]
402
402
  const undeployHdiPlugins = await this._readTypesFromUndeployJson() // see CAP issue #6222
403
403
  const hdiPlugins = new Set([...DEFAULT_HDI_PLUGINS, ...undeployHdiPlugins, ...compileHdiPlugins])
404
404
  let hdiConfigPath
@@ -1,5 +1,7 @@
1
1
  [
2
2
  "src/gen/**/*.hdbview",
3
3
  "src/gen/**/*.hdbindex",
4
- "src/gen/**/*.hdbconstraint"
4
+ "src/gen/**/*.hdbconstraint",
5
+ "src/gen/**/*_drafts.hdbtable",
6
+ "src/gen/**/*.hdbcalculationview"
5
7
  ]
@@ -92,7 +92,7 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
92
92
  */
93
93
  async copySrvContent(src, destRoot, destSrv) {
94
94
  const srvRootBlockList = RegExp('package\\.json$|package-lock\\.json$|\\.npmrc$|\\.cdsrc\\.json$')
95
- const srvBlockList = RegExp('\\.cds$|csn\\.json$|\\.csn$|manifest\\.y.?ml$|\\.env($|\\..*$)|default-env\\.json$')
95
+ const srvBlockList = RegExp('\\.cds$|csn\\.json$|\\.csn$|manifest\\.y.?ml$|\\.env($|\\..*$)|default-env\\.json$|\\.cdsrc-private.json$')
96
96
  const srvRootFileNames = []
97
97
 
98
98
  // 1. copy all files to 'destSrv' except those contained in the blocklist (including node_modules)
@@ -58,6 +58,11 @@ function servicePath(csdl, csn) {
58
58
  if (csdl.$EntityContainer) {
59
59
  const serviceName = csdl.$EntityContainer.replace(/\.[^.]+$/, '')
60
60
  const service = csn.definitions[serviceName]
61
+
62
+ // if @protocol is 'none' then throw an error
63
+ if (service['@protocol'] === 'none') {
64
+ throw new Error(`Service "${serviceName}" is annotated with @protocol:'none' which is not supported in openAPI generation.`)
65
+ }
61
66
  return cds.service.path4?.(service) || cds.serve.path4(service)
62
67
  }
63
68
  }
@@ -13,9 +13,9 @@ const cfUtil = require('../../util/cf');
13
13
  const gitUtil = require('../../util/git');
14
14
  const mtaUtil = require('./mtaUtil');
15
15
 
16
- const { bold, info } = require('../../util/term');
16
+ const { bold, info, warn } = require('../../util/term');
17
17
 
18
- const { bind } = require('../../bind');
18
+ const { bind, storeBindings } = require('../../bind');
19
19
 
20
20
  const DEBUG = cds.debug('deploy');
21
21
 
@@ -37,6 +37,9 @@ module.exports = new class HanaDeployer {
37
37
 
38
38
  async deploy(options = {}) {
39
39
  let { model, serviceName, tunnelAddress, vcapFile, undeployWhitelist, hdiOptions = {}, dry, storeCredentials } = options;
40
+
41
+ options.for = options.for || 'hybrid';
42
+
40
43
  if (model) {
41
44
  throw `Specifying a CDS model for HANA deployment is not supported.
42
45
  'cds deploy --to hana' performs 'cds build --for hana' which in turn uses the configured HANA build task.
@@ -51,13 +54,22 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
51
54
  console.log(`Using VCAP_SERVICES from file ${vcapFile} (beta feature).`);
52
55
  }
53
56
 
54
- const projectPath = cds.root;
57
+ const fromFile = vcapFile && exists(vcapFile) ? await read(vcapFile) : {};
58
+ const fromEnv = this.getFromEnv('VCAP_SERVICES', 'SERVICE_REPLACEMENTS');
59
+ const vcapEnv = { ...fromEnv, ...fromFile };
60
+
61
+ const warnings = [];
62
+ if (serviceName && vcapEnv.VCAP_SERVICES?.hana?.[0].name !== serviceName) {
63
+ if (vcapFile) {
64
+ warnings.push(`The specified service name '${serviceName}' was used; the information coming from --vcap-service file was ignored.`);
65
+ } else if (Object.keys(fromEnv).length > 0) {
66
+ warnings.push(`The specified service name '${serviceName}' was used; the information coming from environment variable VCAP_SERVICES was ignored.`);
67
+ }
68
+ }
55
69
 
70
+ const projectPath = cds.root;
56
71
  const buildResults = await this._build();
57
72
 
58
- const fromFile = vcapFile && exists(vcapFile) ? await read(vcapFile) : {};
59
- const fromEnv = this.getFromEnv('VCAP_SERVICES', 'SERVICE_REPLACEMENTS');
60
- let vcapEnv = { ...fromEnv, ...fromFile };
61
73
  if (process.env.TARGET_CONTAINER) {
62
74
  vcapEnv.TARGET_CONTAINER = process.env.TARGET_CONTAINER;
63
75
  }
@@ -111,7 +123,8 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
111
123
  for: options.for,
112
124
  kind: task.for
113
125
  }
114
- await bind(bindOptions);
126
+ const services = await bind(bindOptions);
127
+ await storeBindings(services, bindOptions);
115
128
  }
116
129
  }
117
130
 
@@ -124,6 +137,11 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
124
137
 
125
138
  console.log(`Done.`);
126
139
 
140
+ if (warnings.length > 0) {
141
+ console.warn(warn('\nWARNING:'));
142
+ console.warn(warn(warnings.join('\n')));
143
+ }
144
+
127
145
  return { buildResults };
128
146
  }
129
147