@sap/cds-dk 4.6.4 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +68 -1
- package/bin/activate.js +5 -1
- package/bin/add.js +2 -1
- package/bin/bind.js +121 -48
- package/bin/cds.js +8 -0
- package/bin/env.js +0 -2
- package/bin/repl.js +5 -2
- package/bin/watch.js +10 -7
- package/lib/bind/bindingResolver.js +26 -14
- package/lib/bind/bindingUtil.js +144 -0
- package/lib/bind/cfKeyProvider.js +2 -80
- package/lib/bind/kubernetesCredentialsProvider.js +157 -0
- package/lib/bind/userCdsrcJsonStorageProvider.js +7 -7
- package/lib/client/activate.js +16 -13
- package/lib/client/base_command.js +5 -1
- package/lib/client/helper/cf_helper.js +5 -2
- package/lib/client/params.js +16 -2
- package/lib/client/settings_manager.js +15 -8
- package/lib/compile/openapi/csdl2openapi.js +6 -8
- package/lib/compile/to-xsuaa.js +0 -14
- package/lib/import/utils/MetadataConverter.js +6 -4
- package/lib/import/utils/MultiMap.js +1 -0
- package/lib/import/v2parser.js +150 -64
- package/lib/import/v4parser.js +118 -56
- package/lib/index.js +6 -5
- package/lib/init/constants.js +11 -5
- package/lib/init/index.js +14 -7
- package/lib/init/template/_merging/existences.js +161 -0
- package/lib/init/template/approuter/files/default-env.json +9 -0
- package/lib/init/template/approuter/files/mta.yaml.hbs +48 -0
- package/lib/init/template/approuter/files/package.json +9 -0
- package/lib/init/template/approuter/files/xs-app.json +8 -0
- package/lib/init/template/approuter/index.js +66 -0
- package/lib/init/template/auditlog/files/dependencies.package.json +5 -0
- package/lib/init/template/auditlog/files/mta.yaml.hbs +13 -0
- package/lib/init/template/auditlog/index.js +38 -0
- package/lib/init/template/cf-manifest/index.js +1 -1
- package/lib/init/template/data/index.js +18 -6
- package/lib/init/template/hana/files/common/undeploy.json +5 -0
- package/lib/init/template/hana/files/java/package.json +3 -0
- package/lib/init/template/hana/files/mta.yaml.hbs +42 -0
- package/lib/init/template/hana/files/nodejs/dependencies.package.json +5 -0
- package/lib/init/template/hana/index.js +53 -24
- package/lib/init/template/java/index.js +1 -5
- package/lib/init/template/lint/index.js +17 -1
- package/lib/init/template/mta/files/mta.yaml.hbs +27 -216
- package/lib/init/template/mta/index.js +25 -17
- package/lib/init/template/mtx/files/cds.package.json +24 -0
- package/lib/init/template/mtx/files/dependencies.package.json +5 -0
- package/lib/init/template/mtx/files/mta.yaml.hbs +74 -0
- package/lib/init/template/mtx/files/xs-security.json.hbs +23 -9
- package/lib/init/template/mtx/index.js +126 -29
- package/lib/init/template/templateBase.js +14 -0
- package/lib/init/template/xsuaa/files/cds.package.json +9 -0
- package/lib/init/template/xsuaa/files/dependencies.package.json +6 -0
- package/lib/init/template/xsuaa/files/mta.yaml.hbs +17 -0
- package/lib/init/template/xsuaa/index.js +75 -0
- package/lib/init/util/fsUtil.js +18 -4
- package/lib/init/util/logger.js +3 -1
- package/lib/init/util/mvnArchetypeUtil.js +3 -1
- package/lib/init/{template/mta → util}/projectReader.js +26 -11
- package/lib/init/util/templateUtil.js +267 -3
- package/lib/lint/Linter.js +1 -2
- package/lib/lint/io.js +3 -12
- package/lib/util/exec.d.ts +54 -0
- package/lib/util/exec.js +210 -0
- package/lib/watch/livereload-server.js +43 -31
- package/lib/watch/node-watch.js +46 -20
- package/lib/watch/open.js +1 -1
- package/npm-shrinkwrap.json +3382 -9106
- package/package.json +8 -9
- package/readme.md +1 -1
- package/lib/init/template/mtx/package/cds.package.json +0 -29
- package/lib/init/template/mtx/package/dependencies.package.json +0 -9
|
@@ -8,18 +8,6 @@ const { URL } = require('url');
|
|
|
8
8
|
const execAsync = util.promisify(cp.exec);
|
|
9
9
|
const IS_WIN = os.platform() === 'win32';
|
|
10
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
11
|
class CFKeyProvider {
|
|
24
12
|
|
|
25
13
|
constructor() {
|
|
@@ -69,7 +57,6 @@ class CFKeyProvider {
|
|
|
69
57
|
|
|
70
58
|
async getCfTargetFromCli() {
|
|
71
59
|
const result = await this._cfRead('target');
|
|
72
|
-
|
|
73
60
|
return {
|
|
74
61
|
apiEndpoint: this.extract(result, /^api endpoint:\s+(.*)$/i),
|
|
75
62
|
user: this.extract(result, /^user:\s+(.*)$/i),
|
|
@@ -142,7 +129,7 @@ class CFKeyProvider {
|
|
|
142
129
|
name = name || `${instance}:${key}`;
|
|
143
130
|
|
|
144
131
|
if (apiEndpoint && apiEndpoint !== target.apiEndpoint) {
|
|
145
|
-
throw new Error(`Current Cloud Foundry api endpoint "${target.apiEndpoint}"
|
|
132
|
+
throw new Error(`Current Cloud Foundry api endpoint "${target.apiEndpoint}" different than api endpoint "${apiEndpoint}" for service binding "${name}".`);
|
|
146
133
|
}
|
|
147
134
|
|
|
148
135
|
let spaceGuid;
|
|
@@ -182,6 +169,7 @@ class CFKeyProvider {
|
|
|
182
169
|
vcap: {
|
|
183
170
|
label: offeringObj.name,
|
|
184
171
|
plan: planObj.name,
|
|
172
|
+
tags: offeringObj.tags
|
|
185
173
|
},
|
|
186
174
|
resolved: true,
|
|
187
175
|
},
|
|
@@ -190,72 +178,6 @@ class CFKeyProvider {
|
|
|
190
178
|
|
|
191
179
|
return resolvedBinding;
|
|
192
180
|
}
|
|
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
181
|
}
|
|
260
182
|
|
|
261
183
|
class BatchRequest {
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
const cp = require('child_process');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const util = require('util');
|
|
4
|
+
const yaml = require("@sap/cds-foss")("yaml");
|
|
5
|
+
|
|
6
|
+
const axios = require('axios');
|
|
7
|
+
const { URL } = require('url');
|
|
8
|
+
|
|
9
|
+
const execFile = util.promisify(cp.execFile);
|
|
10
|
+
const IS_WIN = os.platform() === 'win32';
|
|
11
|
+
|
|
12
|
+
class KubernetesCredentialsProvider {
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
this.logger = console;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
setLogger(logger) {
|
|
19
|
+
this.logger = logger;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async _kubectlRead(...args) {
|
|
23
|
+
this.logger.debug(`_kubectlRead: kubectl ${args.join(" ")}`);
|
|
24
|
+
|
|
25
|
+
const result = await execFile("kubectl", args, {
|
|
26
|
+
shell: IS_WIN,
|
|
27
|
+
stdio: ['inherit', 'pipe', 'inherit'],
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return result.stdout.toString().trim();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async _kubectlConfig() {
|
|
35
|
+
const configString = await this._kubectlRead('config', 'view', '--raw');
|
|
36
|
+
const config = yaml.parse(configString);
|
|
37
|
+
|
|
38
|
+
const currentContextName = config["current-context"];
|
|
39
|
+
if (!currentContextName) throw new Error(`No current context in kubectl config.`);
|
|
40
|
+
const currentContextEntry = config.contexts && config.contexts.find( entry => entry.name === currentContextName && entry.context && entry.context.cluster && entry.context.user );
|
|
41
|
+
if (!currentContextEntry) throw new Error(`Current context not found in kubectl config.`);
|
|
42
|
+
const currentContext = currentContextEntry.context;
|
|
43
|
+
const clusterEntry = config.clusters && config.clusters.find( entry => entry.name === currentContext.cluster && entry.cluster && entry.cluster.server );
|
|
44
|
+
if (!currentContextEntry) throw new Error(`Cluster ${currentContext.cluster} not found in kubectl config.`);
|
|
45
|
+
const cluster = clusterEntry.cluster;
|
|
46
|
+
const userEntry = config.users && config.users.find( entry => entry.name === currentContext.user && entry.user && entry.user.token );
|
|
47
|
+
if (!userEntry) throw new Error(`User ${currentContext.user} not found in kubectl config.`);
|
|
48
|
+
const user = userEntry.user;
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
url: cluster.server,
|
|
52
|
+
namespace: currentContext.namespace || 'default',
|
|
53
|
+
token: user.token
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async init() {
|
|
58
|
+
this.config = await this._kubectlConfig();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async _req(urlPath) {
|
|
62
|
+
|
|
63
|
+
const url = new URL(urlPath, this.config.url);
|
|
64
|
+
this.logger.debug(`_req: ${url.toString()}`);
|
|
65
|
+
|
|
66
|
+
const result = await axios.get(url.toString(), {
|
|
67
|
+
validateStatus: status => status === 200 || status === 404,
|
|
68
|
+
headers: {
|
|
69
|
+
authorization: `Bearer ${this.config.token}`
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (result.status === 200) {
|
|
74
|
+
return result.data;
|
|
75
|
+
} else {
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
async resolve(name, binding) {
|
|
82
|
+
if (!this.initPromise) {
|
|
83
|
+
this.initPromise = this.init();
|
|
84
|
+
}
|
|
85
|
+
await this.initPromise;
|
|
86
|
+
|
|
87
|
+
let {cluster, namespace, name: bindingName, secret } = binding;
|
|
88
|
+
let instance;
|
|
89
|
+
let secretOrBindingResource = 'Secret';
|
|
90
|
+
|
|
91
|
+
if (!namespace) namespace = this.config.namespace;
|
|
92
|
+
if (cluster && cluster !== this.config.url) throw new Error(`Current Kubernetes cluster "${this.config.url}" different than Kubernetes cluster "${cluster}" for service binding "${name}".`);
|
|
93
|
+
|
|
94
|
+
if (bindingName) {
|
|
95
|
+
const bindingResource = await this._req(`/apis/servicecatalog.k8s.io/v1beta1/namespaces/${encodeURIComponent(namespace)}/servicebindings/${encodeURIComponent(bindingName)}`);
|
|
96
|
+
if (bindingResource === null) {
|
|
97
|
+
// Fallback to secret instead of service binding;
|
|
98
|
+
secret = bindingName;
|
|
99
|
+
bindingName = undefined;
|
|
100
|
+
secretOrBindingResource = 'Service Binding or Secret';
|
|
101
|
+
} else {
|
|
102
|
+
secret = bindingResource.spec.secretName || throwError();
|
|
103
|
+
instance = bindingResource.spec.instanceRef && bindingResource.spec.instanceRef.name || throwError();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const secretResource = await this._req(`/api/v1/namespaces/${encodeURIComponent(namespace)}/secrets/${encodeURIComponent(secret)}`);
|
|
108
|
+
if (secretResource === null) throwError(`${secretOrBindingResource} '${secret}' not found.`);
|
|
109
|
+
|
|
110
|
+
const credentials = {};
|
|
111
|
+
for (const name in secretResource.data) {
|
|
112
|
+
let value = secretResource.data[name];
|
|
113
|
+
value = Buffer.from(value, "base64").toString(); // secret values are base64 encoded
|
|
114
|
+
// TODO: TESTIT
|
|
115
|
+
if (value && value[0] === '{') try { value = JSON.parse(value); } catch(e) {/* ignored */} // secret values may contain JSON objects
|
|
116
|
+
credentials[name] = value;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const resolvedBinding = {
|
|
120
|
+
binding: {
|
|
121
|
+
type: "k8s",
|
|
122
|
+
name: bindingName,
|
|
123
|
+
cluster: this.config.url,
|
|
124
|
+
instance,
|
|
125
|
+
namespace,
|
|
126
|
+
secret: secret,
|
|
127
|
+
resolved: true,
|
|
128
|
+
},
|
|
129
|
+
credentials
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if (instance) {
|
|
134
|
+
const serviceInstanceResource = await this._req(`/apis/servicecatalog.k8s.io/v1beta1/namespaces/${encodeURIComponent(namespace)}/serviceinstances/${encodeURIComponent(instance)}`);
|
|
135
|
+
|
|
136
|
+
resolvedBinding.binding.vcap = {
|
|
137
|
+
label: serviceInstanceResource.spec.clusterServiceClassExternalName,
|
|
138
|
+
plan: serviceInstanceResource.spec.clusterServicePlanExternalName
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return resolvedBinding;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
*
|
|
150
|
+
* @param {string} msg
|
|
151
|
+
* @returns {never}
|
|
152
|
+
*/
|
|
153
|
+
function throwError(msg = 'Internal Error') {
|
|
154
|
+
throw new Error(msg);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = new KubernetesCredentialsProvider();
|
|
@@ -3,7 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const readline = require('readline');
|
|
4
4
|
|
|
5
5
|
const { fse } = require('@sap/cds-foss');
|
|
6
|
-
const { highlight
|
|
6
|
+
const { highlight } = require('../util/term');
|
|
7
7
|
|
|
8
8
|
const commandUtil = require('../init/util/commandUtil');
|
|
9
9
|
|
|
@@ -108,19 +108,19 @@ ${file}
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
for (const service of services) {
|
|
111
|
-
const { name, kind, binding, credentials } = service;
|
|
111
|
+
const { name, kind, binding, credentials, vcap } = service;
|
|
112
112
|
const requireService = section[name] = section[name] || {};
|
|
113
113
|
|
|
114
|
-
Object.assign(requireService, {
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
Object.assign(requireService, { binding, credentials });
|
|
115
|
+
if (kind) requireService.kind = kind;
|
|
116
|
+
if (vcap) requireService.vcap = vcap;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
await this.writeJsonFile(jsonPath, jsonObject, options.logger);
|
|
120
120
|
if (options.profile) {
|
|
121
|
-
options.logger.log(`Saving bindings to ${highlight(jsonPath)} in profile ${highlight(options.profile)}
|
|
121
|
+
options.logger.log(`Saving bindings to ${highlight(jsonPath)} in profile ${highlight(options.profile)}`);
|
|
122
122
|
} else {
|
|
123
|
-
options.logger.log(`Saving bindings to ${highlight(jsonPath)}
|
|
123
|
+
options.logger.log(`Saving bindings to ${highlight(jsonPath)}`);
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
|
package/lib/client/activate.js
CHANGED
|
@@ -8,6 +8,7 @@ const { collectFileContent, collectFiles, toUnixPath } = require('./helper/fs_he
|
|
|
8
8
|
const Logger = require('./helper/logging');
|
|
9
9
|
|
|
10
10
|
const COMMAND_URL_SUFFIX = 'asyncActivate';
|
|
11
|
+
const COMMAND_URL_SUFFIX_SYNC = 'activate';
|
|
11
12
|
const UPDATE_CUSTOM_FILE_URL_SUFFIX = 'updateCustomTenantContent';
|
|
12
13
|
|
|
13
14
|
const I18N_WHITELIST = ['.properties', '.csv'];
|
|
@@ -36,6 +37,7 @@ async function checkFinished(jobId, statusUrl, params, finisher) {
|
|
|
36
37
|
module.exports = class Activate extends BaseCommand {
|
|
37
38
|
|
|
38
39
|
static get COMMAND_URL_SUFFIX() { return COMMAND_URL_SUFFIX; }
|
|
40
|
+
static get COMMAND_URL_SUFFIX_SYNC() { return COMMAND_URL_SUFFIX_SYNC; }
|
|
39
41
|
static get UPDATE_CUSTOM_FILE_URL_SUFFIX() { return UPDATE_CUSTOM_FILE_URL_SUFFIX; }
|
|
40
42
|
|
|
41
43
|
static async run(injection, paramValues) {
|
|
@@ -46,7 +48,8 @@ module.exports = class Activate extends BaseCommand {
|
|
|
46
48
|
Logger.log('Activating extension with parameters ' + JSON.stringify({
|
|
47
49
|
appUrl: params.get('appUrl'),
|
|
48
50
|
projectFolder: params.get('projectFolder'),
|
|
49
|
-
undeploy: params.get('undeploy')
|
|
51
|
+
undeploy: params.get('undeploy'),
|
|
52
|
+
sync: params.get('sync')
|
|
50
53
|
}));
|
|
51
54
|
} else {
|
|
52
55
|
Logger.log(`Activating extension located at ${params.get('projectFolder')} to URL ${params.get('appUrl')}`);
|
|
@@ -155,6 +158,7 @@ module.exports = class Activate extends BaseCommand {
|
|
|
155
158
|
|
|
156
159
|
static async runActivate(cds, params) {
|
|
157
160
|
let activationUrl;
|
|
161
|
+
const isSync = params.get('sync');
|
|
158
162
|
|
|
159
163
|
return Activate.collectExtensionFiles(cds, params.get('projectFolder'))
|
|
160
164
|
.then(allFiles => {
|
|
@@ -164,20 +168,20 @@ module.exports = class Activate extends BaseCommand {
|
|
|
164
168
|
|
|
165
169
|
Logger.log('Activating extension...');
|
|
166
170
|
|
|
167
|
-
|
|
171
|
+
const urlSuffix = isSync ? Activate.COMMAND_URL_SUFFIX_SYNC : Activate.COMMAND_URL_SUFFIX;
|
|
172
|
+
activationUrl = Activate.getMtxApiUrl(params.get('appUrl'), urlSuffix);
|
|
168
173
|
const requestOptions = Object.assign({
|
|
169
|
-
headers: {}
|
|
170
|
-
data: {
|
|
171
|
-
extension: [...allFiles],
|
|
172
|
-
undeployExtension: params.get('undeploy')
|
|
173
|
-
}
|
|
174
|
+
headers: {}
|
|
174
175
|
}, params.get('reqAuth'));
|
|
175
176
|
requestOptions.headers['Content-Type'] = 'application/json';
|
|
176
177
|
|
|
177
|
-
return axios.post(activationUrl,
|
|
178
|
+
return axios.post(activationUrl, {
|
|
179
|
+
extension: [...allFiles],
|
|
180
|
+
undeployExtension: params.get('undeploy')
|
|
181
|
+
}, requestOptions);
|
|
178
182
|
})
|
|
179
183
|
.then(response => {
|
|
180
|
-
if (response === null) {
|
|
184
|
+
if (isSync || response === null) {
|
|
181
185
|
return;
|
|
182
186
|
}
|
|
183
187
|
|
|
@@ -223,13 +227,12 @@ module.exports = class Activate extends BaseCommand {
|
|
|
223
227
|
const customFileUrl = Activate.getMtxApiUrl(params.get('appUrl'), Activate.UPDATE_CUSTOM_FILE_URL_SUFFIX);
|
|
224
228
|
const requestOptions = Object.assign({
|
|
225
229
|
headers: {},
|
|
226
|
-
data: {
|
|
227
|
-
content: customFilesSerialized
|
|
228
|
-
}
|
|
229
230
|
}, params.get('reqAuth'));
|
|
230
231
|
requestOptions.headers['Content-Type'] = 'application/json';
|
|
231
232
|
|
|
232
|
-
await axios.post(customFileUrl,
|
|
233
|
+
await axios.post(customFileUrl, {
|
|
234
|
+
content: customFilesSerialized
|
|
235
|
+
}, requestOptions);
|
|
233
236
|
|
|
234
237
|
Logger.log('Custom file upload finished');
|
|
235
238
|
}
|
|
@@ -19,7 +19,7 @@ module.exports = class BaseCommand {
|
|
|
19
19
|
throw error;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
switch (error.response.status) {
|
|
22
|
+
switch (error.response && error.response.status) {
|
|
23
23
|
case 401:
|
|
24
24
|
Logger.error('Request failed: authorization might have expired. Please retry with valid passcode' +
|
|
25
25
|
(params.has('passcodeUrl') ? ` from ${params.get('passcodeUrl')}` : ''), error);
|
|
@@ -52,4 +52,8 @@ module.exports = class BaseCommand {
|
|
|
52
52
|
static getMtxApiUrl(url, action) {
|
|
53
53
|
return BaseCommand.concatUrls(url, MTX_URL_PATH_PREFIX, action);
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
static getMtxApiUrlSync(url, action) {
|
|
57
|
+
return BaseCommand.concatUrls(url, MTX_URL_PATH_PREFIX, action);
|
|
58
|
+
}
|
|
55
59
|
}
|
|
@@ -154,12 +154,13 @@ async function getSubdomain(appName) {
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
Logger.debug(`Subdomain determined from CF app environment: ${value}`);
|
|
157
158
|
return resolve(value);
|
|
158
159
|
});
|
|
159
160
|
});
|
|
160
161
|
|
|
161
162
|
function errorResult(message) {
|
|
162
|
-
Logger.debug(message);
|
|
163
|
+
Logger.debug(`Failed to get subdomain: ${message}`);
|
|
163
164
|
return undefined;
|
|
164
165
|
}
|
|
165
166
|
}
|
|
@@ -174,8 +175,10 @@ async function getAppName(appUrl) {
|
|
|
174
175
|
const app = apps.find(app => equalIgnoringTls(app.url, appUrl));
|
|
175
176
|
if (!app) {
|
|
176
177
|
Logger.debug(`Failed to find app with URL ${appUrl}. Running apps: ${apps.map(app => app.name)}`);
|
|
178
|
+
return;
|
|
177
179
|
}
|
|
178
|
-
|
|
180
|
+
Logger.debug(`App name determined from CF: ${app.name}`);
|
|
181
|
+
return app.name;
|
|
179
182
|
}
|
|
180
183
|
|
|
181
184
|
module.exports = {
|
package/lib/client/params.js
CHANGED
|
@@ -20,7 +20,7 @@ const ObfuscationLevel = Object.freeze({
|
|
|
20
20
|
full: 1,
|
|
21
21
|
partial: 2 // Allows a few start and end characters displayed in DEBUG output; only use with string-type params
|
|
22
22
|
});
|
|
23
|
-
const DISCLOSURE_LENGTH =
|
|
23
|
+
const DISCLOSURE_LENGTH = 2;
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Parameters hold a name and a value and can only be set to compatible values.
|
|
@@ -205,6 +205,7 @@ class AllParams extends BasicParams {
|
|
|
205
205
|
// `activate` PARAMS
|
|
206
206
|
this.undeploy = new Param('undeploy', false);
|
|
207
207
|
this.wsdl = new Param('wsdl', false);
|
|
208
|
+
this.sync = new Param('sync', false);
|
|
208
209
|
|
|
209
210
|
// `logout` PARAMS
|
|
210
211
|
this.deleteSettings = new Param('deleteSettings', false);
|
|
@@ -331,7 +332,7 @@ class ParamCollection {
|
|
|
331
332
|
}
|
|
332
333
|
|
|
333
334
|
/**
|
|
334
|
-
* Add all params contained in another collection to this.
|
|
335
|
+
* Add all params contained in another collection to this, replacing existing params.
|
|
335
336
|
* @param params the other `ParamCollection`
|
|
336
337
|
*/
|
|
337
338
|
merge(params) {
|
|
@@ -341,6 +342,19 @@ class ParamCollection {
|
|
|
341
342
|
}
|
|
342
343
|
}
|
|
343
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Add all params contained in another collection to this, not replacing existing params.
|
|
347
|
+
* @param params the other `ParamCollection`
|
|
348
|
+
*/
|
|
349
|
+
mergeLower(params) {
|
|
350
|
+
this.checkFrozen();
|
|
351
|
+
for (const param of params) {
|
|
352
|
+
if (!this.has(param.name)) {
|
|
353
|
+
this.set(param.name, param.val);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
344
358
|
/**
|
|
345
359
|
* Iterate through all contained param objects.
|
|
346
360
|
*/
|
|
@@ -71,13 +71,18 @@ async function getAppUrlAndSubdomainFromSuggestions(params) {
|
|
|
71
71
|
params.set('appUrl', app.url);
|
|
72
72
|
|
|
73
73
|
if (!params.has('subdomain')) {
|
|
74
|
-
params.set('subdomain', await
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
params.set('subdomain', await getSubdomainFromCfAndNotify(app.name));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function getSubdomainFromCfAndNotify(appName) {
|
|
79
|
+
const subdomain = await getSubdomain(appName);
|
|
80
|
+
if (subdomain) {
|
|
81
|
+
Logger.log(`Subdomain determined from CF app environment: ${subdomain}`);
|
|
82
|
+
} else {
|
|
83
|
+
Logger.log('Failed to determine subdomain from CF app environment');
|
|
80
84
|
}
|
|
85
|
+
return subdomain;
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
function getTokenBaseUrl(params, renewUrl) {
|
|
@@ -156,7 +161,7 @@ class SettingsManager {
|
|
|
156
161
|
const loadedSettings = await this._loadFromFile(projectFolder);
|
|
157
162
|
Logger.debug(`Loaded project settings: ${loadedSettings.format()}`);
|
|
158
163
|
|
|
159
|
-
params.
|
|
164
|
+
params.mergeLower(loadedSettings);
|
|
160
165
|
|
|
161
166
|
await this.updateUrls(params, logout);
|
|
162
167
|
await this.checkKeytar(params, logout);
|
|
@@ -172,12 +177,13 @@ class SettingsManager {
|
|
|
172
177
|
}
|
|
173
178
|
if (!logout) {
|
|
174
179
|
if (!params.has('appUrl')) {
|
|
180
|
+
Logger.debug('App URL not given');
|
|
175
181
|
await getAppUrlAndSubdomainFromSuggestions(params);
|
|
176
182
|
} else if (!params.has('subdomain')) {
|
|
177
183
|
Logger.debug('Subdomain not given');
|
|
178
184
|
const appName = await getAppName(params.get('appUrl'));
|
|
179
185
|
if (appName) {
|
|
180
|
-
params.set('subdomain', await
|
|
186
|
+
params.set('subdomain', await getSubdomainFromCfAndNotify(appName));
|
|
181
187
|
}
|
|
182
188
|
}
|
|
183
189
|
}
|
|
@@ -189,6 +195,7 @@ class SettingsManager {
|
|
|
189
195
|
if (!params.has('subdomain')) {
|
|
190
196
|
throw new Error('Subdomain not given');
|
|
191
197
|
}
|
|
198
|
+
Logger.log(`Subdomain determined from app URL: ${params.get('subdomain')}`);
|
|
192
199
|
}
|
|
193
200
|
}
|
|
194
201
|
function renewTokenUrl() {
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Converts OData CSDL JSON to OpenAPI 3.0.2
|
|
3
|
-
*
|
|
4
|
-
* Latest version: https://github.wdf.sap.corp/d037427/odata-openapi/blob/master/lib/csdl2openapi.js
|
|
5
3
|
*/
|
|
6
4
|
|
|
7
5
|
var pluralize = require('pluralize')
|
|
@@ -776,7 +774,7 @@ module.exports.csdl2openapi = function (
|
|
|
776
774
|
|
|
777
775
|
/**
|
|
778
776
|
* Unpack EnumMember value if it uses CSDL JSON CS01 style, like CAP does
|
|
779
|
-
* @param {string or object} path Qualified name of referenced type
|
|
777
|
+
* @param {string or object} path Qualified name of referenced type
|
|
780
778
|
* @return {object} Reference Object
|
|
781
779
|
*/
|
|
782
780
|
function enumMember(member) {
|
|
@@ -788,7 +786,7 @@ module.exports.csdl2openapi = function (
|
|
|
788
786
|
|
|
789
787
|
/**
|
|
790
788
|
* Unpack NavigationPropertyPath value if it uses CSDL JSON CS01 style, like CAP does
|
|
791
|
-
* @param {string or object} path Qualified name of referenced type
|
|
789
|
+
* @param {string or object} path Qualified name of referenced type
|
|
792
790
|
* @return {object} Reference Object
|
|
793
791
|
*/
|
|
794
792
|
function navigationPropertyPath(path) {
|
|
@@ -800,7 +798,7 @@ module.exports.csdl2openapi = function (
|
|
|
800
798
|
|
|
801
799
|
/**
|
|
802
800
|
* Unpack PropertyPath value if it uses CSDL JSON CS01 style, like CAP does
|
|
803
|
-
* @param {string or object} path Qualified name of referenced type
|
|
801
|
+
* @param {string or object} path Qualified name of referenced type
|
|
804
802
|
* @return {object} Reference Object
|
|
805
803
|
*/
|
|
806
804
|
function propertyPath(path) {
|
|
@@ -2147,7 +2145,7 @@ module.exports.csdl2openapi = function (
|
|
|
2147
2145
|
|
|
2148
2146
|
/**
|
|
2149
2147
|
* Augment OpenAPI document with Security Requirements Object
|
|
2150
|
-
* @param {object} openapi OpenAPI document to augment
|
|
2148
|
+
* @param {object} openapi OpenAPI document to augment
|
|
2151
2149
|
* @param {object} entityContainer Entity Container object
|
|
2152
2150
|
*/
|
|
2153
2151
|
function security(openapi, entityContainer) {
|
|
@@ -2162,7 +2160,7 @@ module.exports.csdl2openapi = function (
|
|
|
2162
2160
|
|
|
2163
2161
|
/**
|
|
2164
2162
|
* a qualified name consists of a namespace or alias, a dot, and a simple name
|
|
2165
|
-
* @param {string} qualifiedName
|
|
2163
|
+
* @param {string} qualifiedName
|
|
2166
2164
|
* @return {string} namespace-qualified name
|
|
2167
2165
|
*/
|
|
2168
2166
|
function namespaceQualifiedName(qualifiedName) {
|
|
@@ -2172,7 +2170,7 @@ module.exports.csdl2openapi = function (
|
|
|
2172
2170
|
|
|
2173
2171
|
/**
|
|
2174
2172
|
* a qualified name consists of a namespace or alias, a dot, and a simple name
|
|
2175
|
-
* @param {string} qualifiedName
|
|
2173
|
+
* @param {string} qualifiedName
|
|
2176
2174
|
* @return {object} with components qualifier and name
|
|
2177
2175
|
*/
|
|
2178
2176
|
function nameParts(qualifiedName) {
|
package/lib/compile/to-xsuaa.js
CHANGED
|
@@ -107,8 +107,6 @@ module.exports = function compile_to_xsuaa (model, options) {
|
|
|
107
107
|
|
|
108
108
|
function mergeConfig(base, generated) {
|
|
109
109
|
const result = base
|
|
110
|
-
result.xsappname = base.xsappname || appName()
|
|
111
|
-
result['tenant-mode'] = base['tenant-mode'] || 'dedicated'
|
|
112
110
|
|
|
113
111
|
const mergeByName = (type) => {
|
|
114
112
|
if (!generated[type]) return
|
|
@@ -140,15 +138,3 @@ function getBaseModel(options = {}) {
|
|
|
140
138
|
}
|
|
141
139
|
return options.base || {}
|
|
142
140
|
}
|
|
143
|
-
|
|
144
|
-
function appName() {
|
|
145
|
-
const cwd = cds.root || process.cwd()
|
|
146
|
-
let name
|
|
147
|
-
try {
|
|
148
|
-
name = require(path.resolve(cwd,'package.json')).name
|
|
149
|
-
} catch (err) { // no package.json
|
|
150
|
-
name = path.basename(cwd)
|
|
151
|
-
}
|
|
152
|
-
// remove any @foo/ from @foo/bar, truncate to max length
|
|
153
|
-
return name.split('/').pop().substring(0, 100)
|
|
154
|
-
}
|
|
@@ -396,7 +396,6 @@ class Expression {
|
|
|
396
396
|
const target = this.getTarget();
|
|
397
397
|
const element = this.getElement();
|
|
398
398
|
const attribs = element.attributes;
|
|
399
|
-
let custom_namespace = MetadataConverter.custom_namespace;
|
|
400
399
|
if (attribs == null) return;
|
|
401
400
|
let realType = attribs.Type || attribs.EntityType || attribs.UnderlyingType;
|
|
402
401
|
const propertyName = attribs.UnderlyingType == null ? '$Type' : '$UnderlyingType';
|
|
@@ -574,6 +573,9 @@ class Expression {
|
|
|
574
573
|
!(target.$Kind === 'NavigationProperty' && target.$Collection === true)) {
|
|
575
574
|
target.$Nullable = true;
|
|
576
575
|
}
|
|
576
|
+
this.includeExternalAnnotations(element,target);
|
|
577
|
+
}
|
|
578
|
+
includeExternalAnnotations(element,target){
|
|
577
579
|
const nonStdAttributes = this.getNonStdAttributes(element.attributes);
|
|
578
580
|
for (let i = 0; i < nonStdAttributes.length; i++) {
|
|
579
581
|
let attr = nonStdAttributes[i];
|
|
@@ -581,7 +583,7 @@ class Expression {
|
|
|
581
583
|
if(attrName)
|
|
582
584
|
attrName = attrName.replace(/-/g,'.');
|
|
583
585
|
let attribute;
|
|
584
|
-
if ((custom_namespace.includes(prefix) && attrName !== undefined) || custom_namespace.includes(true))
|
|
586
|
+
if ((MetadataConverter.custom_namespace.includes(prefix) && attrName !== undefined) || MetadataConverter.custom_namespace.includes(true))
|
|
585
587
|
attribute = "@" + prefix + "." + attrName;
|
|
586
588
|
else if (attrName === undefined)
|
|
587
589
|
attribute = "$" + prefix;
|
|
@@ -2221,10 +2223,10 @@ class EntityContainerExpression extends Expression {
|
|
|
2221
2223
|
interpret() {
|
|
2222
2224
|
const element = this.getElement();
|
|
2223
2225
|
const target = { $Kind: 'EntityContainer' };
|
|
2226
|
+
this.includeExternalAnnotations(element,target);
|
|
2224
2227
|
this.setTarget(target);
|
|
2225
2228
|
super.interpret();
|
|
2226
|
-
|
|
2227
|
-
return { [element.attributes.Name]: target };
|
|
2229
|
+
return { [element.attributes.Name]: target};
|
|
2228
2230
|
}
|
|
2229
2231
|
}
|
|
2230
2232
|
|