@sap/cds-dk 4.5.4 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/bin/bind.js +124 -8
- package/bin/cds-ts.js +1 -1
- package/bin/cds.js +1 -0
- package/bin/env.js +50 -9
- package/bin/help.js +1 -0
- package/bin/import.js +34 -14
- package/bin/watch.js +25 -4
- package/lib/bind/bindingResolver.js +138 -0
- package/lib/bind/cfKeyProvider.js +353 -0
- package/lib/bind/userCdsrcJsonStorageProvider.js +144 -0
- package/lib/client/auth_manager.js +4 -4
- package/lib/client/settings_manager.js +1 -1
- package/lib/import/common.js +10 -1
- package/lib/import/importapi.js +4 -3
- package/lib/import/index.js +34 -5
- package/lib/import/message.js +1 -0
- package/lib/import/utils/MetadataConverter.js +25 -4
- package/lib/import/utils/MetadataConverterFactory.js +1 -2
- package/lib/import/utils/MultiMap.js +26 -0
- package/lib/import/v2parser.js +131 -12
- package/lib/import/v4parser.js +212 -172
- package/lib/init/template/lint/files/.eslint/rules/no-entity-moo.js +36 -52
- package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/rule.test.js +15 -0
- package/lib/init/template/lint/index.js +14 -6
- package/lib/init/template/mta/files/mta.yaml.hbs +0 -7
- package/lib/init/template/mtx/files/xs-security.json.hbs +3 -1
- package/lib/init/template/mtx/package/dependencies.package.json +1 -1
- package/lib/init/template/nodejs/files/.gitignore.hbs +3 -0
- package/lib/init/util/commandUtil.js +5 -0
- package/lib/lint/Linter.js +31 -20
- package/lib/lint/checks.js +5 -1
- package/lib/lint/io.js +59 -35
- package/lib/util/term.js +2 -0
- package/lib/watch/node-watch.js +38 -19
- package/npm-shrinkwrap.json +1782 -1138
- package/package.json +3 -3
- package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/test.js +0 -10
|
@@ -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();
|
|
@@ -26,12 +26,12 @@ function getTokenUrl(params) {
|
|
|
26
26
|
tokenUrl.search = `subdomain=${params.get('subdomain')}`;
|
|
27
27
|
|
|
28
28
|
if (params.has('refreshToken')) {
|
|
29
|
-
tokenUrl.search += `&refresh_token=${params.get('refreshToken')}`;
|
|
29
|
+
tokenUrl.search += `&refresh_token=${encodeURIComponent(params.get('refreshToken'))}`;
|
|
30
30
|
} else if (params.has('passcode')) {
|
|
31
|
-
tokenUrl.search += `&passcode=${params.get('passcode')}`;
|
|
31
|
+
tokenUrl.search += `&passcode=${encodeURIComponent(params.get('passcode'))}`;
|
|
32
32
|
}
|
|
33
33
|
if (params.has('clientid')) {
|
|
34
|
-
tokenUrl.search += `&clientid=${params.get('clientid')}&clientsecret=${params.get('clientsecret')}`;
|
|
34
|
+
tokenUrl.search += `&clientid=${encodeURIComponent(params.get('clientid'))}&clientsecret=${encodeURIComponent(params.get('clientsecret'))}`;
|
|
35
35
|
}
|
|
36
36
|
return tokenUrl.toString();
|
|
37
37
|
}
|
|
@@ -58,7 +58,7 @@ async function retrieveTokenOrPasscodeUrl(params) {
|
|
|
58
58
|
|
|
59
59
|
let response;
|
|
60
60
|
try {
|
|
61
|
-
response = await axios(tokenUrl
|
|
61
|
+
response = await axios(tokenUrl, { method: 'GET' });
|
|
62
62
|
} catch (error) {
|
|
63
63
|
throw new Error(`Failed to get authentication token: ${error}.\nResponse body: ${util.inspect(response && response.data)}`);
|
|
64
64
|
}
|
|
@@ -55,7 +55,7 @@ function other(tokenStorage) {
|
|
|
55
55
|
|
|
56
56
|
function getProjectFolder(params) {
|
|
57
57
|
const relativePath = params.get('projectFolder') || params.get('directory') || (
|
|
58
|
-
params.has('subdomain') && fse.existsSync(
|
|
58
|
+
params.has('subdomain') && fse.existsSync(params.get('subdomain'))
|
|
59
59
|
? params.get('subdomain')
|
|
60
60
|
: '.'
|
|
61
61
|
);
|
package/lib/import/common.js
CHANGED
|
@@ -30,6 +30,14 @@ function checkForEmptyKeys(element, oDataVersion) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
//to replace the alias value with original schema namespace value
|
|
34
|
+
function replaceAliasValue(schemaContent, schemaNamespaceValue, aliasValue){
|
|
35
|
+
const re = RegExp(`([("/])${aliasValue}([/").])`, 'g');
|
|
36
|
+
const val = `$1${schemaNamespaceValue}$2`;
|
|
37
|
+
schemaContent = schemaContent.replace(re, val);
|
|
38
|
+
return JSON.parse(schemaContent);
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
async function _getODataVersion(edmx) {
|
|
34
42
|
let oDataVersion;
|
|
35
43
|
let jsonObj;
|
|
@@ -115,5 +123,6 @@ module.exports = {
|
|
|
115
123
|
getVersion,
|
|
116
124
|
getSuffix,
|
|
117
125
|
supportedFormats,
|
|
118
|
-
checkForEmptyKeys
|
|
126
|
+
checkForEmptyKeys,
|
|
127
|
+
replaceAliasValue
|
|
119
128
|
};
|
package/lib/import/importapi.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const edm2csn = require('.');
|
|
2
|
-
|
|
2
|
+
let context={};
|
|
3
|
+
context.oDataInfo = {};
|
|
4
|
+
context.namespaces = [];
|
|
3
5
|
/**
|
|
4
6
|
* This is the import API function to generate CSN from XML/EDMX files.
|
|
5
7
|
* @param {string} filepath
|
|
@@ -10,11 +12,10 @@ const edm2csn = require('.');
|
|
|
10
12
|
* - other import options not supported
|
|
11
13
|
* @returns {object} CSN
|
|
12
14
|
*/
|
|
13
|
-
const oDataInfo={};
|
|
14
15
|
module.exports = async function cds_import(filepath, options = {keepNamespace : false}) //namespace will be taken as false in case it is not mentioned
|
|
15
16
|
{
|
|
16
17
|
filepath = await edm2csn.isValidInputFile(filepath);
|
|
17
18
|
let edmx= await edm2csn.getMetadataFromFile(filepath);
|
|
18
|
-
let csn = await edm2csn.edmx2csn(edmx, !options.keepNamespace && filepath,
|
|
19
|
+
let csn = await edm2csn.edmx2csn(edmx, !options.keepNamespace && filepath, context);
|
|
19
20
|
return csn;
|
|
20
21
|
}
|
package/lib/import/index.js
CHANGED
|
@@ -116,7 +116,15 @@ function _convertEDMXWithMultipleSchemas(edmx, multipleSchemas, namespace) {
|
|
|
116
116
|
</Schema>
|
|
117
117
|
</edmx:DataServices>
|
|
118
118
|
*/
|
|
119
|
-
|
|
119
|
+
|
|
120
|
+
// Remove the ending and beginning of the first and second schema tag
|
|
121
|
+
edmx = edmx.replace(multipleSchemas, '');
|
|
122
|
+
// Now capture the schema tag details
|
|
123
|
+
let schemaDetails = edmx.match(/(<Schema [^>]*>)/);
|
|
124
|
+
// Replace the Namespace value with the correct one
|
|
125
|
+
schemaDetails = schemaDetails[0].replace(/Namespace="[^"]+"/, `Namespace="${namespace}"`);
|
|
126
|
+
// Place the above computed schema details in the edmx content
|
|
127
|
+
edmx = edmx.replace(/(<Schema [^>]*>)/, `${schemaDetails}`);
|
|
120
128
|
|
|
121
129
|
return edmx;
|
|
122
130
|
}
|
|
@@ -132,7 +140,7 @@ async function edmx2csn(edmx, filename, context) {
|
|
|
132
140
|
if (multipleSchemas.test(edmx)) {
|
|
133
141
|
edmx = _convertEDMXWithMultipleSchemas(edmx, multipleSchemas, namespace);
|
|
134
142
|
}
|
|
135
|
-
let csn = await generateCSN(edmx, true, context);
|
|
143
|
+
let csn = await generateCSN(edmx, false, true, context);
|
|
136
144
|
if (filename) {
|
|
137
145
|
// Replace namespace by given filename in the csn file instead of edmx file
|
|
138
146
|
const re = RegExp(`([("/])${namespace}([/").])`, 'g');
|
|
@@ -151,20 +159,33 @@ async function _generateEDMX2JSON(edmx) {
|
|
|
151
159
|
return JSON.parse(cjson);
|
|
152
160
|
}
|
|
153
161
|
|
|
154
|
-
async function generateCSN(edmx, mockServerUc, context) {
|
|
162
|
+
async function generateCSN(edmx, ignorePersistenceSkip, mockServerUc, context) {
|
|
155
163
|
let csn;
|
|
156
164
|
let odataVersion;
|
|
157
165
|
let edmx2jsonModel;
|
|
166
|
+
let defaultNamespaces;
|
|
167
|
+
let namespaces = [];
|
|
168
|
+
if(context.namespaces) namespaces.push(...(context.namespaces));
|
|
158
169
|
odataVersion = await common.getVersion(edmx);
|
|
159
170
|
if (odataVersion === "V2") {
|
|
160
171
|
if(context){
|
|
161
172
|
context.oDataVersion = "V2";
|
|
162
173
|
}
|
|
174
|
+
//default namespaces to be present
|
|
175
|
+
if(!context.include_all_namespaces)
|
|
176
|
+
{
|
|
177
|
+
defaultNamespaces = ['edmx','sap','m'];
|
|
178
|
+
namespaces = namespaces.concat(defaultNamespaces);
|
|
179
|
+
namespaces = namespaces.filter((item,pos) => namespaces.indexOf(item) === pos); //removing duplicate values
|
|
180
|
+
}
|
|
181
|
+
context.namespaces = namespaces;
|
|
163
182
|
edmx2jsonModel = await _generateEDMX2JSON(edmx);
|
|
164
183
|
return new Promise(function getCsn(resolve, reject) {
|
|
165
184
|
csn = edmxv2CSN.getEdmxv2CSN(
|
|
166
185
|
edmx2jsonModel,
|
|
167
|
-
|
|
186
|
+
ignorePersistenceSkip,
|
|
187
|
+
mockServerUc,
|
|
188
|
+
context
|
|
168
189
|
);
|
|
169
190
|
if (csn) {
|
|
170
191
|
resolve(csn);
|
|
@@ -177,7 +198,15 @@ async function generateCSN(edmx, mockServerUc, context) {
|
|
|
177
198
|
if(context){
|
|
178
199
|
context.oDataVersion = "V4";
|
|
179
200
|
}
|
|
180
|
-
|
|
201
|
+
//default namespaces to be present
|
|
202
|
+
if(!context.include_all_namespaces)
|
|
203
|
+
{
|
|
204
|
+
defaultNamespaces = ['edmx'];
|
|
205
|
+
namespaces = namespaces.concat(defaultNamespaces);
|
|
206
|
+
namespaces = namespaces.filter((item,pos) => namespaces.indexOf(item) === pos); //removing duplicate values
|
|
207
|
+
}
|
|
208
|
+
context.namespaces = namespaces;
|
|
209
|
+
return edmxv4CSN.getEdmxv4CSN(edmx, ignorePersistenceSkip, mockServerUc, context);
|
|
181
210
|
}
|
|
182
211
|
return new Promise(function invalidFile(resolve, reject) {
|
|
183
212
|
reject(new Error(messages.INVALID_EDMX_METADATA));
|
package/lib/import/message.js
CHANGED
|
@@ -13,6 +13,7 @@ const messages = {
|
|
|
13
13
|
OUTPUT_FILE_MISMATCH: 'Specify the file extension that --as option supports.',
|
|
14
14
|
INVALID_AS_OPTION: 'For --as option, please specify one of these output file format [cds, csn, json].',
|
|
15
15
|
INCORRECT_EXTENSION: 'If --as option is not provided, then please provide either .json or .csn output file extension.',
|
|
16
|
+
INCORRECT_INPUT_NAMESPACES: 'Invalid value for --include-namespaces option. Specify namespaces as comma-separated list or "*"'
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
function getMessages() {
|