@sap/cds-dk 4.5.4 → 4.6.4

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 (48) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/bin/bind.js +124 -8
  3. package/bin/cds-ts.js +1 -1
  4. package/bin/cds.js +1 -0
  5. package/bin/compile/index.js +2 -1
  6. package/bin/env.js +50 -9
  7. package/bin/help.js +1 -0
  8. package/bin/import.js +34 -14
  9. package/bin/repl.js +6 -2
  10. package/bin/watch.js +40 -10
  11. package/lib/bind/bindingResolver.js +138 -0
  12. package/lib/bind/cfKeyProvider.js +353 -0
  13. package/lib/bind/userCdsrcJsonStorageProvider.js +144 -0
  14. package/lib/client/activate.js +3 -9
  15. package/lib/client/auth_manager.js +4 -4
  16. package/lib/client/extend.js +4 -9
  17. package/lib/client/settings_manager.js +6 -5
  18. package/lib/import/common.js +10 -1
  19. package/lib/import/importapi.js +4 -3
  20. package/lib/import/index.js +37 -7
  21. package/lib/import/message.js +1 -0
  22. package/lib/import/utils/MetadataConverter.js +25 -4
  23. package/lib/import/utils/MetadataConverterFactory.js +1 -2
  24. package/lib/import/utils/MultiMap.js +26 -0
  25. package/lib/import/v2parser.js +138 -16
  26. package/lib/import/v4parser.js +212 -172
  27. package/lib/init/constants.js +1 -1
  28. package/lib/init/template/data/index.js +11 -1
  29. package/lib/init/template/hana/index.js +16 -20
  30. package/lib/init/template/lint/files/.eslint/rules/no-entity-moo.js +36 -52
  31. package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/rule.test.js +15 -0
  32. package/lib/init/template/lint/index.js +19 -17
  33. package/lib/init/template/mta/files/mta.yaml.hbs +0 -7
  34. package/lib/init/template/mta/projectReader.js +1 -3
  35. package/lib/init/template/mtx/files/xs-security.json.hbs +3 -1
  36. package/lib/init/template/mtx/package/dependencies.package.json +1 -1
  37. package/lib/init/template/nodejs/files/.gitignore.hbs +1 -0
  38. package/lib/init/template/samples/index.js +1 -1
  39. package/lib/init/template/templateBase.js +10 -41
  40. package/lib/init/util/commandUtil.js +5 -0
  41. package/lib/lint/Linter.js +19 -28
  42. package/lib/lint/generators.js +5 -4
  43. package/lib/lint/io.js +76 -51
  44. package/lib/util/term.js +2 -0
  45. package/lib/watch/node-watch.js +38 -19
  46. package/npm-shrinkwrap.json +1053 -1279
  47. package/package.json +3 -3
  48. package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/test.js +0 -10
@@ -0,0 +1,138 @@
1
+ const Config = require('@sap/cds/lib/env')
2
+ const cds = require('@sap/cds/lib');
3
+ const LOG = cds.log('bind');
4
+
5
+ function getUnresolvedBindings({cwd = process.cwd(), env}) {
6
+ if (!env) env = Config.for('cds', cwd)
7
+ if (feature(env) === false || feature(env) === "false") return {}
8
+
9
+ const requires = env.requires || {}
10
+ let bindings = {}
11
+ for (const name in requires) {
12
+ const service = requires[name]
13
+ if (service && service.binding && !service.binding.resolved) {
14
+ bindings[name] = {...service.binding, kind: service.kind }
15
+ }
16
+ }
17
+
18
+ return bindings
19
+ }
20
+
21
+ async function resolveBindings({bindings, cwd = process.cwd(), env, output = 'info', cache = {} }) {
22
+ const config = { requires: { } }
23
+ bindings = bindings || getUnresolvedBindings({cwd, env})
24
+ if (Object.keys(bindings).length===0) return config;
25
+ LOG[output](`resolving cloud service bindings...`)
26
+ await Promise.all( Object.keys(bindings).map( async name => {
27
+ const binding = bindings[name]
28
+ const cacheKey = JSON.stringify(binding)
29
+ if (!cache[cacheKey]) {
30
+ cache[cacheKey] = config.requires[name] = await resolveBinding(name, binding)
31
+ LOG[output](`bound ${name} to Cloud Foundry service instance ${binding.instance}`)
32
+ } else {
33
+ config.requires[name] = cache[cacheKey]
34
+ }
35
+ } ));
36
+
37
+ // Add kind to result, so that it can be applied to old @sap/cds versions that do not read `.cdsrc-private.json`
38
+ for (const name in config.requires) {
39
+ if (bindings[name].kind) config.requires[name].kind = bindings[name].kind
40
+ }
41
+
42
+ return config
43
+ }
44
+
45
+ let cfKeyProvider
46
+
47
+ async function resolveBinding(name, binding) {
48
+ switch (binding.type) {
49
+ case 'cf':
50
+ if (!cfKeyProvider) {
51
+ cfKeyProvider = require('./cfKeyProvider');
52
+ cfKeyProvider.setLogger(LOG);
53
+ }
54
+ return cfKeyProvider.resolve(name, binding);
55
+ default:
56
+ throw new Error(`Unsupported binding type "${binding.type}" for service "${name}".`)
57
+ }
58
+ }
59
+
60
+ async function applyTo(options) {
61
+ const config = await resolveBindings(options)
62
+ if (!config) return
63
+ const {env} = options
64
+ for (const name in config.requires) {
65
+ const service = config.requires[name]
66
+ const { binding, credentials } = service
67
+ Object.assign(env.requires[name], { binding, credentials })
68
+ }
69
+
70
+ if (Config._emulate_vcap_services) {
71
+ Config._emulate_vcap_services()
72
+ }
73
+ }
74
+
75
+ class BindingManager {
76
+ constructor(options) {
77
+ this.cache = {}
78
+ this.bindingsStr = ''
79
+ this.options = options
80
+ }
81
+
82
+ getBindings() {
83
+ const bindings = getUnresolvedBindings(this.options)
84
+ return bindings
85
+ }
86
+
87
+ async resolve(bindings) {
88
+ if (!bindings) {
89
+ bindings = this.getBindings(this.options)
90
+ this.bindingsStr = JSON.stringify(bindings)
91
+ }
92
+
93
+ const config = await resolveBindings({...this.options, bindings, cache: this.cache})
94
+ if (config && Object.keys(config.requires || {}).length > 0) return config;
95
+ }
96
+
97
+ async update({ onBeforeUpdate, onAfterUpdate } = {}) {
98
+ const oldBindingsStr = this.bindingsStr
99
+ const bindings = this.getBindings()
100
+ const bindingsStr = JSON.stringify(bindings)
101
+ this.bindingsStr = bindingsStr
102
+
103
+ if (this.bindingsStr === oldBindingsStr) return // No change
104
+ if (onBeforeUpdate) await onBeforeUpdate()
105
+
106
+ try {
107
+ if (this.pendingUpdate) await this.pendingUpdate
108
+ } catch(error) {
109
+ // ignore
110
+ }
111
+ this.pendingUpdate = undefined
112
+
113
+ if (this.bindingsStr !== bindingsStr) return // Next update in progress
114
+
115
+ let resolvedBindings
116
+ try {
117
+ this.pendingUpdate = this.resolve(bindings)
118
+ resolvedBindings = await this.pendingUpdate
119
+ } catch(error) {
120
+ LOG.error(error.message)
121
+ return
122
+ }
123
+
124
+ if (this.bindingsStr !== bindingsStr) return // Next update in progress
125
+ if (onAfterUpdate) onAfterUpdate(resolvedBindings)
126
+ }
127
+ }
128
+
129
+ function feature(env) {
130
+ return env.features && env.features.dk && env.features.dk.resolveBindingsForTesting
131
+ }
132
+
133
+ module.exports = {
134
+ applyTo,
135
+ bindingManager: function(options) {
136
+ return new BindingManager(options)
137
+ }
138
+ }
@@ -0,0 +1,353 @@
1
+ const cp = require('child_process');
2
+ const os = require('os');
3
+ const util = require('util');
4
+
5
+ const axios = require('axios');
6
+ const { URL } = require('url');
7
+
8
+ const execAsync = util.promisify(cp.exec);
9
+ const IS_WIN = os.platform() === 'win32';
10
+
11
+ const KindToRequiresNameMap = {
12
+ xsuaa: 'uaa',
13
+ hana: 'db',
14
+ destinations: 'destinations',
15
+ connectivity: 'connectivity',
16
+ 'audit-log': 'audit-log',
17
+ 'enterprise-messaging': 'messaging',
18
+ 'enterprise-messaging-amqp': 'messaging',
19
+ 'enterprise-messaging-http': 'messaging',
20
+ 'enterprise-messaging-shared': 'messaging'
21
+ }
22
+
23
+ class CFKeyProvider {
24
+
25
+ constructor() {
26
+ this.logger = console;
27
+ }
28
+
29
+ async _cfRead(...args) {
30
+ this.logger.debug(`_cfRead: cf ${args}`);
31
+
32
+ const result = await execAsync(`cf ${args}`, {
33
+ shell: IS_WIN,
34
+ stdio: ['inherit', 'pipe', 'inherit'],
35
+ });
36
+
37
+ return result.stdout.toString().trim();
38
+ }
39
+
40
+ async _req(target, urlPath) {
41
+ const url = new URL(urlPath, target.apiEndpoint);
42
+
43
+ this.logger.debug(`_req: ${url.toString()}`);
44
+
45
+ const result = await axios.get(url.toString(), {
46
+ headers: {
47
+ authorization: target.authorization
48
+ }
49
+ });
50
+
51
+ return result.data;
52
+ }
53
+
54
+ extract(string, pattern, defaultValue) {
55
+ const array = string.split(/[\n\r]+/);
56
+ for (let line of array) {
57
+ const match = line.match(pattern);
58
+ if (match) {
59
+ return match[1];
60
+ }
61
+ }
62
+
63
+ if (typeof defaultValue === 'undefined') {
64
+ throw Error(`Pattern not found: ${pattern}`);
65
+ } else {
66
+ return defaultValue;
67
+ }
68
+ }
69
+
70
+ async getCfTargetFromCli() {
71
+ const result = await this._cfRead('target');
72
+
73
+ return {
74
+ apiEndpoint: this.extract(result, /^api endpoint:\s+(.*)$/i),
75
+ user: this.extract(result, /^user:\s+(.*)$/i),
76
+ org: this.extract(result, /^org:\s+(.*)$/i),
77
+ space: this.extract(result, /^space:\s+(.*)$/i),
78
+ };
79
+ }
80
+
81
+ async getCfSpaceInfo() {
82
+ this.logger.debug('getting space info');
83
+
84
+ const target = await this.getCfTargetFromCli();
85
+ const authorization = await this.getCfAuthorization();
86
+ const targetWithAuth = Object.assign({}, target, { authorization });
87
+
88
+
89
+ const { org, space } = target;
90
+ const orgs = await this._req(targetWithAuth, `/v3/organizations?names=${org}`);
91
+ if (!(orgs && orgs.resources.length === 1)) {
92
+ throw new Error(`CF org ${org} not found!`);
93
+ }
94
+
95
+ const orgGuid = orgs.resources[0].guid;
96
+ const spaces = await this._req(targetWithAuth, `/v3/spaces?names=${space}&organization_guids=${orgGuid}`)
97
+ if (!(spaces && spaces.resources.length === 1)) {
98
+ throw new Error(`CF space ${space} not found in org ${org}!`);
99
+ }
100
+
101
+ const spaceGuid = spaces.resources[0].guid;
102
+
103
+ return Object.assign({}, target, { orgGuid, spaceGuid, authorization });
104
+ }
105
+
106
+ async getCfAuthorization() {
107
+ this.logger.debug('getting authorization');
108
+ return this._cfRead('oauth-token');
109
+ }
110
+
111
+ setLogger(logger) {
112
+ this.logger = logger;
113
+ }
114
+
115
+ async init() {
116
+ const target = this.target = await this.getCfSpaceInfo();
117
+ const logger = this.logger;
118
+ this.getOrg = BatchRequest.create({logger, target, url: '/v3/organizations'});
119
+ this.getSpace = BatchRequest.create({logger, target, url: '/v3/spaces', extractors: {
120
+ organization_guids: resource => resource.relationships.organization.data.guid
121
+ }});
122
+ this.getInstance = BatchRequest.create({logger, target, url: '/v3/service_instances', extractors: {
123
+ space_guids: resource => resource.relationships.space.data.guid
124
+ }});
125
+ this.getKey = BatchRequest.create({logger, target, url: '/v3/service_credential_bindings', extractors: {
126
+ service_instance_guids: resource => resource.relationships.service_instance.data.guid
127
+ }});
128
+ this.getPlans = BatchRequest.create({logger, target, url: '/v3/service_plans' });
129
+ this.getOfferings = BatchRequest.create({logger, target, url: '/v3/service_offerings' });
130
+ }
131
+
132
+ async resolve(name, binding) {
133
+ if (!this.initPromise) {
134
+ this.initPromise = this.init();
135
+ }
136
+ await this.initPromise;
137
+
138
+ const target = this.target;
139
+
140
+ let {instance, key, apiEndpoint, org, space} = binding;
141
+ key = key || `${instance}-key`;
142
+ name = name || `${instance}:${key}`;
143
+
144
+ if (apiEndpoint && apiEndpoint !== target.apiEndpoint) {
145
+ throw new Error(`Current Cloud Foundry api endpoint "${target.apiEndpoint}" doesn't match with requested api endpoint "${apiEndpoint}" for service binding "${name}".`);
146
+ }
147
+
148
+ let spaceGuid;
149
+ if (org && space) {
150
+ const orgObj = await this.getOrg({ names: org });
151
+ const spaceObj = await this.getSpace({ names: space, organization_guids: orgObj.guid });
152
+ spaceGuid = spaceObj.guid;
153
+ } else {
154
+ org = target.org;
155
+ space = target.space;
156
+ spaceGuid = target.spaceGuid;
157
+ }
158
+
159
+ let instanceObj;
160
+ try {
161
+ instanceObj = await this.getInstance({ names: instance, space_guids: spaceGuid });
162
+ } catch(error) {
163
+ throw new Error(`Service instance "${instance}" not found.`);
164
+ }
165
+ let keyObj;
166
+ try {
167
+ keyObj = await this.getKey({names: key, service_instance_guids: instanceObj.guid });
168
+ } catch(error) {
169
+ throw new Error(`No service key "${key}" found for service instance "${instance}".`);
170
+ }
171
+ const planObj = await this.getPlans({guids: instanceObj.relationships.service_plan.data.guid});
172
+ const offeringObj = await this.getOfferings({guids: planObj.relationships.service_offering.data.guid });
173
+ const keyCredentialsObj = await req(this.logger, target, `/v3/service_credential_bindings/${encodeURIComponent(keyObj.guid)}/details`);
174
+ const resolvedBinding = {
175
+ binding: {
176
+ type: "cf",
177
+ apiEndpoint: target.apiEndpoint,
178
+ org: org,
179
+ space: space,
180
+ instance: instance,
181
+ key: key,
182
+ vcap: {
183
+ label: offeringObj.name,
184
+ plan: planObj.name,
185
+ },
186
+ resolved: true,
187
+ },
188
+ credentials: keyCredentialsObj.credentials
189
+ };
190
+
191
+ return resolvedBinding;
192
+ }
193
+
194
+ getRequiresNameFromKind(service, kind) {
195
+ const requiresName = KindToRequiresNameMap[kind];
196
+ if (!requiresName) throw new Error(`Unknown CDS service name for service "${service}". Please specify as argument for "cds bind".`)
197
+ return requiresName;
198
+ }
199
+
200
+ /**
201
+ * Determine kind value for services based on VCAP binding
202
+ * information in the configuration.
203
+ */
204
+ determineKinds(services) {
205
+ const vcapServices = {}
206
+
207
+ const getIndex = service => {
208
+ const { credentials } = service;
209
+ return credentials && credentials.binding && credentials.binding.index;
210
+ }
211
+
212
+ // Build VCAP_SERVICES
213
+ for (let index = 0; index<services.length; index++) {
214
+ const service = services[index];
215
+ const { label, name, tags } = service.binding.vcap;
216
+ if (!vcapServices[label]) vcapServices[label] = [];
217
+ vcapServices[label].push({ credentials: { binding: { index } }, label, name, tags });
218
+
219
+ service.kindCandidates = [ ]
220
+ service.nameCandidates = [ ]
221
+ }
222
+
223
+ // Auto configure services based on VCAP_SERVICES
224
+ const cds = require('@sap/cds/lib');
225
+ const VCAP_SERVICES = process.env.VCAP_SERVICES;
226
+ try {
227
+ process.env.VCAP_SERVICES = JSON.stringify(vcapServices);
228
+ const env = cds.env.for('cds', '.');
229
+
230
+ // Extract "kind" from auto configured services
231
+ for (const kind of Object.keys(env.requires)) {
232
+ const requireService = env.requires[kind];
233
+ const superKind = requireService.kind;
234
+ const index = getIndex(requireService);
235
+ if (!Number.isInteger(index)) continue;
236
+
237
+ const superRequireService = env.requires[superKind];
238
+ if (superRequireService) {
239
+ const superIndex = getIndex(superRequireService);
240
+ if (index === superIndex) continue;
241
+ }
242
+
243
+ services[index].kindCandidates.push(kind);
244
+ }
245
+
246
+ // Check if one "kind" per service was found
247
+ for (const service of services) {
248
+ if (service.kindCandidates.length !== 1) {
249
+ throw new Error(`Unknown kind for service "${service.binding.instance}". Please specify with "--kind" option.`);
250
+ }
251
+
252
+ service.kind = service.kindCandidates[0];
253
+ delete service.kindCandidates;
254
+ }
255
+ } finally {
256
+ process.env.VCAP_SERVICES = VCAP_SERVICES;
257
+ }
258
+ }
259
+ }
260
+
261
+ class BatchRequest {
262
+ constructor({logger, target, url, extractors = {}}) {
263
+ this.logger = logger;
264
+ this.target = target;
265
+ this.url = url;
266
+ this.filters = [];
267
+ this.timeout = undefined;
268
+ this.extractors = Object.assign({ names: resource => resource.name, guids: resource => resource.guid }, extractors);
269
+ }
270
+
271
+ static create(options) {
272
+ const obj = new BatchRequest(options);
273
+ return (...args) => obj.request(...args);
274
+ }
275
+
276
+ async request(filter) {
277
+ let resolve;
278
+ let reject;
279
+ const promise = new Promise( (_resolve, _reject) => {
280
+ resolve = _resolve;
281
+ reject = _reject;
282
+ });
283
+ this.filters.push({ filter, resolve, reject });
284
+
285
+ if (this.timeout) clearTimeout(this.timeout);
286
+ this.timeout = setTimeout(() => this.executeRequest(), 0);
287
+
288
+ return promise;
289
+ }
290
+
291
+ async executeRequest() {
292
+ const filters = this.filters;
293
+ this.filters = [];
294
+ const filterParams = {};
295
+ for (const filter of filters) {
296
+ for (const name of Object.keys(filter.filter)) {
297
+ if (!filterParams[name]) filterParams[name] = [];
298
+ filterParams[name].push(filter.filter[name]);
299
+ }
300
+ }
301
+
302
+ const params = Object.keys(filterParams).map(
303
+ name => `${name}=${filterParams[name].map( value => encodeURIComponent(value) ).join(',')}`
304
+ ).join("&");
305
+
306
+ const url = this.url + (this.url.match(/\?/) ? '&' : '?') + params;
307
+
308
+ let data;
309
+ try {
310
+ data = await this.httpRequest(url);
311
+ } catch (error) {
312
+ for (const filter of filters) {
313
+ filter.reject(error);
314
+ }
315
+ return;
316
+ }
317
+ const fmt = filter => Object.keys(filter).map( key => `${key}: ${filter[key]}` ).join(', ');
318
+ const resources = data.resources;
319
+ for (const filter of filters) {
320
+ let filteredResources = resources;
321
+ for (const name of Object.keys(filter.filter)) {
322
+ filteredResources = filteredResources.filter( resource => this.extractors[name](resource) === filter.filter[name] );
323
+ }
324
+ if (filteredResources.length === 1) {
325
+ filter.resolve(filteredResources[0]);
326
+ } else if (filteredResources.length === 0) {
327
+ filter.reject(new Error(`Couldn't find resource for url "${this.url}" with filter "${fmt(filter.filter)}"`));
328
+ } else {
329
+ filter.reject(new Error(`Found more than one resources for url "${this.url}" with filter "${fmt(filter.filter)}"`));
330
+ }
331
+ }
332
+ }
333
+
334
+ async httpRequest(urlPath) {
335
+ return req(this.logger, this.target, urlPath);
336
+ }
337
+ }
338
+
339
+ async function req(logger, target, urlPath) {
340
+ const url = new URL(urlPath, target.apiEndpoint);
341
+
342
+ logger.debug(`_req: ${url.toString()}`);
343
+
344
+ const result = await axios.get(url.toString(), {
345
+ headers: {
346
+ authorization: target.authorization
347
+ }
348
+ });
349
+
350
+ return result.data;
351
+ }
352
+
353
+ module.exports = new CFKeyProvider();
@@ -0,0 +1,144 @@
1
+ const os = require('os');
2
+ const path = require('path');
3
+ const readline = require('readline');
4
+
5
+ const { fse } = require('@sap/cds-foss');
6
+ const { highlight, warn } = require('../util/term');
7
+
8
+ const commandUtil = require('../init/util/commandUtil');
9
+
10
+ const CDSRC_FILENAME = '.cdsrc-private.json';
11
+ const GIT_IGNORE_FILE = '.gitignore';
12
+
13
+ class UserCdsrcJsonStorageProvider {
14
+
15
+ async askBooleanQuestion(question) {
16
+ const rl = readline.createInterface({
17
+ input: process.stdin,
18
+ output: process.stdout
19
+ });
20
+
21
+ question = question.trim() + ' ';
22
+ return new Promise(resolve => {
23
+ rl.question(question, answer => {
24
+ if (answer === 'y' || answer === 'Y') {
25
+ resolve(true);
26
+ } else {
27
+ resolve(false);
28
+ }
29
+ rl.close();
30
+ });
31
+ });
32
+ }
33
+
34
+ async ensureFileIsGitignored(file, logger = console) {
35
+ try {
36
+ await commandUtil.spawnCommand('git', ['check-ignore', '-n', '-v', file], {}, undefined, true);
37
+ return; // file is git ignored
38
+ } catch (err) {
39
+ if (err.code === 'ENOENT') { // git command not found
40
+ logger.debug('git command not found');
41
+ return;
42
+ }
43
+
44
+ if (!err.stdLog.match(/::/)) { // included or not a git repo
45
+ logger.debug(err);
46
+ return;
47
+ }
48
+ }
49
+
50
+ logger.log(`adding entry '${file}' to ${GIT_IGNORE_FILE}.`);
51
+ let gitignore = await this.readFileSafely(GIT_IGNORE_FILE);
52
+ gitignore = gitignore + `
53
+ # added by cds bind
54
+ ${file}
55
+ `;
56
+ await fse.writeFile(GIT_IGNORE_FILE, gitignore, 'utf8');
57
+ }
58
+
59
+ async readFileSafely(file) {
60
+ try {
61
+ return await fse.readFile(file, 'utf8');
62
+ } catch (err) {
63
+ return '';
64
+ }
65
+ }
66
+
67
+ async storeServices(services, options = {}) {
68
+ let jsonPath = options['output-file'] || CDSRC_FILENAME;
69
+
70
+ if (jsonPath === CDSRC_FILENAME) {
71
+ await this.ensureFileIsGitignored(CDSRC_FILENAME, options.logger);
72
+ }
73
+
74
+ jsonPath = jsonPath.replace(/~/g, os.homedir());
75
+ // const stat = await fse.stat(jsonPath);
76
+ if (path.extname(jsonPath) !== '.json') {
77
+ jsonPath = path.join(jsonPath, '.cdsrc.json');
78
+ }
79
+
80
+ // if (options['save-credentials'] && services.find(service => service.credentials) && process.stdin.isTTY) {
81
+ // const answer = await this.askBooleanQuestion(warn(`IF YOU CONTINUE, SENSITIVE DATA WILL BE WRITTEN TO AN UNENCRYPTED FILE [${jsonPath}]! Do you want to continue [y/N] ?`));
82
+ // if (!answer) {
83
+ // options.logger.log(`aborting cds bind; not writing any sensitive data to file [${jsonPath}]`);
84
+ // return;
85
+ // }
86
+ // }
87
+
88
+ const jsonObject = await this.readJsonSafely(jsonPath);
89
+ let cdsRoot;
90
+ if (path.basename(jsonPath) === 'package.json') {
91
+ jsonObject.cds = jsonObject.cds || {};
92
+ cdsRoot = jsonObject.cds;
93
+ } else {
94
+ cdsRoot = jsonObject;
95
+ }
96
+
97
+ const profileKey = `[${options.profile}]`;
98
+ cdsRoot.features = cdsRoot.features || {};
99
+ cdsRoot.features[profileKey] = cdsRoot.features[profileKey] || {};
100
+ cdsRoot.features[profileKey].emulate_vcap_services = true;
101
+
102
+ cdsRoot.requires = cdsRoot.requires || {};
103
+ let section;
104
+ if (options.profile) {
105
+ section = cdsRoot.requires[profileKey] = cdsRoot.requires[profileKey] || {};
106
+ } else {
107
+ section = cdsRoot.requires;
108
+ }
109
+
110
+ for (const service of services) {
111
+ const { name, kind, binding, credentials } = service;
112
+ const requireService = section[name] = section[name] || {};
113
+
114
+ Object.assign(requireService, { kind, binding, credentials });
115
+
116
+ options.logger.log(`Binding ${highlight(name)} to Cloud Foundry service ${highlight(binding.instance)} key ${highlight(binding.key)} with kind ${highlight(kind)}.`);
117
+ }
118
+
119
+ await this.writeJsonFile(jsonPath, jsonObject, options.logger);
120
+ if (options.profile) {
121
+ options.logger.log(`Saving bindings to ${highlight(jsonPath)} in profile ${highlight(options.profile)} .`);
122
+ } else {
123
+ options.logger.log(`Saving bindings to ${highlight(jsonPath)}.`);
124
+ }
125
+ }
126
+
127
+ async readJsonSafely(filePath) {
128
+ try {
129
+ return await fse.readJSON(filePath) || {};
130
+ } catch (err) {
131
+ return {};
132
+ }
133
+ }
134
+
135
+ async writeJsonFile(filePath, content) {
136
+ await fse.outputJSON(filePath, content, {
137
+ spaces: 2
138
+ });
139
+
140
+ await fse.chmod(filePath, 0o600); // -rw-------
141
+ }
142
+ }
143
+
144
+ module.exports = new UserCdsrcJsonStorageProvider();
@@ -20,11 +20,7 @@ const JOB_STATUS = {
20
20
  };
21
21
 
22
22
  async function checkFinished(jobId, statusUrl, params, finisher) {
23
- const requestOptions = Object.assign({
24
- method: 'GET'
25
- }, params.get('reqAuth'));
26
-
27
- const { status, error } = (await axios(statusUrl, requestOptions)).data;
23
+ const { status, error } = (await axios.get(statusUrl, params.get('reqAuth'))).data;
28
24
 
29
25
  if ((status === JOB_STATUS.FINISHED || status === JOB_STATUS.FAILED) && !finisher.finished) {
30
26
  finisher.finished = true;
@@ -170,7 +166,6 @@ module.exports = class Activate extends BaseCommand {
170
166
 
171
167
  activationUrl = Activate.getMtxApiUrl(params.get('appUrl'), COMMAND_URL_SUFFIX);
172
168
  const requestOptions = Object.assign({
173
- method: 'POST',
174
169
  headers: {},
175
170
  data: {
176
171
  extension: [...allFiles],
@@ -179,7 +174,7 @@ module.exports = class Activate extends BaseCommand {
179
174
  }, params.get('reqAuth'));
180
175
  requestOptions.headers['Content-Type'] = 'application/json';
181
176
 
182
- return axios(activationUrl, requestOptions);
177
+ return axios.post(activationUrl, requestOptions);
183
178
  })
184
179
  .then(response => {
185
180
  if (response === null) {
@@ -227,7 +222,6 @@ module.exports = class Activate extends BaseCommand {
227
222
 
228
223
  const customFileUrl = Activate.getMtxApiUrl(params.get('appUrl'), Activate.UPDATE_CUSTOM_FILE_URL_SUFFIX);
229
224
  const requestOptions = Object.assign({
230
- method: 'POST',
231
225
  headers: {},
232
226
  data: {
233
227
  content: customFilesSerialized
@@ -235,7 +229,7 @@ module.exports = class Activate extends BaseCommand {
235
229
  }, params.get('reqAuth'));
236
230
  requestOptions.headers['Content-Type'] = 'application/json';
237
231
 
238
- await axios(customFileUrl, requestOptions);
232
+ await axios.post(customFileUrl, requestOptions);
239
233
 
240
234
  Logger.log('Custom file upload finished');
241
235
  }