@sap/cds-dk 7.6.0 → 7.6.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.
- package/bin/import.js +2 -2
- package/lib/deploy/to-hana/hana.js +46 -76
- package/lib/deploy/to-hana/hdiDeployUtil.js +15 -18
- package/lib/deploy/to-hana/mtaUtil.js +1 -1
- package/lib/import/common.js +18 -23
- package/lib/import/odata/metadataconverter/MetadataConverter.js +12 -8
- package/lib/import/odata/v4parser.js +67 -12
- package/lib/init/constants.js +1 -3
- package/lib/lint/Linter.js +2 -2
- package/lib/lint/checks.js +0 -51
- package/lib/mtx/auth_manager.js +46 -55
- package/lib/mtx/settings_manager.js +14 -19
- package/lib/mtx/util/dependencies.js +1 -1
- package/lib/mtx/util/logging.js +10 -8
- package/lib/util/axios.js +5 -0
- package/npm-shrinkwrap.json +339 -311
- package/package.json +1 -10
package/bin/import.js
CHANGED
|
@@ -87,7 +87,7 @@ async function _import(file, options = {}) {
|
|
|
87
87
|
if (!file) return this.help('import');
|
|
88
88
|
// process options
|
|
89
89
|
await common.processOptions(options, apiOptions);
|
|
90
|
-
const
|
|
90
|
+
const srcFilePath = await common.preProcess(file, options, cwd);
|
|
91
91
|
|
|
92
92
|
// process - csn generation
|
|
93
93
|
// if --from option is given, call api directly accordingly
|
|
@@ -102,5 +102,5 @@ async function _import(file, options = {}) {
|
|
|
102
102
|
options.inputFileKind = apiOptions.odataVersion ? apiOptions.odataVersion : apiOptions.inputFileKind;
|
|
103
103
|
|
|
104
104
|
// postprocess
|
|
105
|
-
return await common.postProcess(
|
|
105
|
+
return await common.postProcess(file, options, csn, cwd);
|
|
106
106
|
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
const cp = require('child_process');
|
|
2
1
|
const path = require('path');
|
|
3
|
-
const util = require('util');
|
|
4
2
|
|
|
5
3
|
const fs = require('fs').promises;
|
|
6
4
|
|
|
7
5
|
const cds = require('../../../lib/cds');
|
|
6
|
+
const { exists, read } = cds.utils;
|
|
8
7
|
|
|
9
8
|
const { build } = require('../../build');
|
|
10
9
|
const buildConstants = require('../../build/constants');
|
|
11
10
|
|
|
12
|
-
const
|
|
11
|
+
const hdi = require('./hdiDeployUtil');
|
|
13
12
|
const cfUtil = require('../../util/cf');
|
|
14
13
|
const gitUtil = require('../../util/git');
|
|
15
14
|
const mtaUtil = require('./mtaUtil');
|
|
@@ -18,15 +17,9 @@ const { bold, info } = require('../../util/term');
|
|
|
18
17
|
|
|
19
18
|
const { bind } = require('../../bind');
|
|
20
19
|
|
|
21
|
-
const
|
|
20
|
+
const DEBUG = cds.debug('deploy');
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
class HanaDeployer {
|
|
25
|
-
|
|
26
|
-
constructor() {
|
|
27
|
-
this.execAsync = util.promisify(cp.exec);
|
|
28
|
-
this.spawnSync = cp.spawnSync;
|
|
29
|
-
}
|
|
22
|
+
module.exports = new class HanaDeployer {
|
|
30
23
|
|
|
31
24
|
getFromEnv(...varNames) {
|
|
32
25
|
const result = {}
|
|
@@ -42,11 +35,8 @@ class HanaDeployer {
|
|
|
42
35
|
return result;
|
|
43
36
|
}
|
|
44
37
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
async deploy(options = {}) { // NOSONAR
|
|
48
|
-
let { model, serviceName, tunnelAddress, vcapFile, undeployWhitelist, hdiOptions, dry } = options;
|
|
49
|
-
|
|
38
|
+
async deploy(options = {}) {
|
|
39
|
+
let { model, serviceName, tunnelAddress, vcapFile, undeployWhitelist, hdiOptions = {}, dry, storeCredentials } = options;
|
|
50
40
|
if (model) {
|
|
51
41
|
throw `Specifying a CDS model for HANA deployment is not supported.
|
|
52
42
|
'cds deploy --to hana' performs 'cds build --for hana' which in turn uses the configured HANA build task.
|
|
@@ -65,15 +55,16 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
|
|
|
65
55
|
|
|
66
56
|
const buildResults = await this._build();
|
|
67
57
|
|
|
68
|
-
|
|
69
|
-
|
|
58
|
+
const fromFile = vcapFile && exists(vcapFile) ? await read(vcapFile) : {};
|
|
59
|
+
const fromEnv = this.getFromEnv('VCAP_SERVICES', 'SERVICE_REPLACEMENTS');
|
|
60
|
+
let vcapEnv = { ...fromFile, ...fromEnv };
|
|
70
61
|
if (process.env.TARGET_CONTAINER) {
|
|
71
|
-
|
|
62
|
+
vcapEnv.TARGET_CONTAINER = process.env.TARGET_CONTAINER;
|
|
72
63
|
}
|
|
73
64
|
|
|
74
65
|
if (dry) {
|
|
75
|
-
for (const
|
|
76
|
-
const model = await cds.load(
|
|
66
|
+
for (const { task } of buildResults) {
|
|
67
|
+
const model = await cds.load(task.src);
|
|
77
68
|
const data = cds.compile.to.hdbtable(model);
|
|
78
69
|
for (const [src, { file }] of data) {
|
|
79
70
|
console.log(`-- ${file}`);
|
|
@@ -82,49 +73,47 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
|
|
|
82
73
|
}
|
|
83
74
|
}
|
|
84
75
|
} else {
|
|
85
|
-
for (const
|
|
86
|
-
|
|
87
|
-
let serviceKeyName;
|
|
88
|
-
const currentModelFolder = buildResult.task.dest;
|
|
76
|
+
for (const { task } of buildResults) {
|
|
77
|
+
const dest = task.dest;
|
|
89
78
|
|
|
90
79
|
if (undeployWhitelist) {
|
|
91
80
|
console.log('Writing undeploy.json');
|
|
92
|
-
await fs.write(path.join(
|
|
81
|
+
await fs.write(path.join(dest, 'undeploy.json'), JSON.stringify(undeployWhitelist, null, 2));
|
|
93
82
|
}
|
|
94
83
|
|
|
95
84
|
const hasVCAPEnv = Object.keys(vcapEnv).length > 0;
|
|
96
85
|
if (!serviceName && hasVCAPEnv) {
|
|
97
|
-
await fs.mkdir(
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
await this._getOrCreateCFService(projectPath,
|
|
101
|
-
serviceKeyName = cfServiceInstanceKeyName;
|
|
102
|
-
serviceName = serviceName || cfServiceInstanceName;
|
|
86
|
+
await fs.mkdir(dest, { recursive: true });
|
|
87
|
+
}
|
|
88
|
+
const { cfServiceInstanceName, cfServiceInstanceKeyName, serviceKey } =
|
|
89
|
+
await this._getOrCreateCFService(projectPath, dest, serviceName, tunnelAddress);
|
|
103
90
|
|
|
104
|
-
|
|
91
|
+
serviceName ??= cfServiceInstanceName;
|
|
92
|
+
if (!vcapEnv.VCAP_SERVICES || serviceName) {
|
|
93
|
+
vcapEnv.VCAP_SERVICES = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
|
|
94
|
+
}
|
|
105
95
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
96
|
+
if (storeCredentials) {
|
|
97
|
+
await this._addInstanceToDefaultEnvJson([dest, projectPath], cfServiceInstanceName, serviceKey);
|
|
109
98
|
}
|
|
110
99
|
|
|
111
100
|
console.log(`${bold('Starting deployment to SAP HANA ...')}`);
|
|
112
101
|
|
|
113
|
-
|
|
114
|
-
await
|
|
102
|
+
DEBUG?.({ dest, vcapEnv, hdiOptions })
|
|
103
|
+
await hdi.deploy(dest, vcapEnv, hdiOptions);
|
|
115
104
|
|
|
116
105
|
if (!hasVCAPEnv) {
|
|
117
106
|
const bindOptions = {
|
|
118
107
|
...options,
|
|
119
|
-
to: `${serviceName}:${
|
|
108
|
+
to: `${serviceName}:${cfServiceInstanceKeyName}`,
|
|
120
109
|
for: options.for,
|
|
121
|
-
kind:
|
|
110
|
+
kind: task.for
|
|
122
111
|
}
|
|
123
112
|
await bind(bindOptions);
|
|
124
113
|
}
|
|
125
114
|
}
|
|
126
115
|
|
|
127
|
-
if (
|
|
116
|
+
if (storeCredentials) {
|
|
128
117
|
await gitUtil.ensureFileIsGitignored('default-env.json', projectPath);
|
|
129
118
|
}
|
|
130
119
|
|
|
@@ -155,7 +144,7 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
|
|
|
155
144
|
this._validateServiceInstanceName(cfServiceInstanceName);
|
|
156
145
|
console.log(`Using container ${bold(cfServiceInstanceName)}`);
|
|
157
146
|
|
|
158
|
-
|
|
147
|
+
const cfConfig = cfServiceInstanceMta?.parameters?.config;
|
|
159
148
|
const serviceInstance = await this.createHanaService(cfServiceInstanceName, cfConfig);
|
|
160
149
|
|
|
161
150
|
const cfServiceInstanceKeyName = `${cfServiceInstanceName}-key`;
|
|
@@ -191,13 +180,17 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
|
|
|
191
180
|
|
|
192
181
|
_validateServiceKey(serviceKey, cfServiceInstanceKey) {
|
|
193
182
|
if (!serviceKey) {
|
|
194
|
-
throw
|
|
183
|
+
throw `Could not create service key ${bold(cfServiceInstanceKey)}.`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (serviceKey['sm_url']) {
|
|
187
|
+
throw `Service key credentials are matching to a Service Manager instance of a multitenant environment. Make sure there is no conflict with ${cfServiceInstanceKey}.`;
|
|
195
188
|
}
|
|
196
189
|
|
|
197
190
|
const fields = ['schema', 'user', 'password', 'url'];
|
|
198
191
|
for (const field of fields) {
|
|
199
192
|
if (!serviceKey[field]) {
|
|
200
|
-
throw
|
|
193
|
+
throw `Service key is missing mandatory field '${field}'. Make sure you are ${bold('not')} using a managed service.`;
|
|
201
194
|
}
|
|
202
195
|
}
|
|
203
196
|
}
|
|
@@ -209,40 +202,21 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
|
|
|
209
202
|
}
|
|
210
203
|
|
|
211
204
|
console.log(`Running build`);
|
|
212
|
-
|
|
213
|
-
return buildResults;
|
|
205
|
+
return build(buildTaskOptions);
|
|
214
206
|
}
|
|
215
207
|
|
|
216
|
-
async _loadEnvFile(defaultEnvFile) {
|
|
217
|
-
try {
|
|
218
|
-
const content = await fs.readFile(defaultEnvFile);
|
|
219
|
-
return JSON.parse(content);
|
|
220
|
-
} catch (err) {
|
|
221
|
-
throw new Error(`Error reading default env file: ${err.message}`);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
208
|
async _addInstanceToDefaultEnvJson(currentFolders, serviceInstanceName, serviceKey) {
|
|
227
209
|
for (const currentFolder of currentFolders) {
|
|
228
|
-
let defaultEnvJson = {};
|
|
229
210
|
const defaultEnvJsonPath = path.join(currentFolder, 'default-env.json');
|
|
211
|
+
const defaultEnvJson = exists(defaultEnvJsonPath) ? await read(defaultEnvJsonPath) : {};
|
|
230
212
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
// ignore any errors
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
defaultEnvJson.VCAP_SERVICES = defaultEnvJson.VCAP_SERVICES || {};
|
|
238
|
-
for (const serviceKey of Object.keys(defaultEnvJson.VCAP_SERVICES)) {
|
|
239
|
-
defaultEnvJson.VCAP_SERVICES[serviceKey] = defaultEnvJson.VCAP_SERVICES[serviceKey].filter((service) => {
|
|
240
|
-
return (service.name !== serviceInstanceName);
|
|
241
|
-
});
|
|
213
|
+
defaultEnvJson.VCAP_SERVICES ??= {};
|
|
214
|
+
for (const [serviceKey, services] of Object.entries(defaultEnvJson.VCAP_SERVICES)) {
|
|
215
|
+
defaultEnvJson.VCAP_SERVICES[serviceKey] = services.filter(service => service.name !== serviceInstanceName);
|
|
242
216
|
}
|
|
243
217
|
|
|
244
218
|
const hanaEntry = this._getVCAPServicesEntry(serviceInstanceName, serviceKey)
|
|
245
|
-
Object.assign(defaultEnvJson, hanaEntry);
|
|
219
|
+
Object.assign(defaultEnvJson.VCAP_SERVICES, hanaEntry);
|
|
246
220
|
|
|
247
221
|
console.log(`Writing ${defaultEnvJsonPath}`);
|
|
248
222
|
await fs.mkdir(path.dirname(defaultEnvJsonPath), { recursive: true })
|
|
@@ -253,15 +227,13 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
|
|
|
253
227
|
|
|
254
228
|
_getVCAPServicesEntry(serviceInstanceName, serviceKey) {
|
|
255
229
|
return {
|
|
256
|
-
VCAP_SERVICES: {
|
|
257
230
|
hana: [
|
|
258
231
|
{
|
|
259
232
|
name: serviceInstanceName,
|
|
260
233
|
tags: ['hana'],
|
|
261
234
|
credentials: serviceKey
|
|
262
235
|
}
|
|
263
|
-
|
|
264
|
-
}
|
|
236
|
+
]
|
|
265
237
|
};
|
|
266
238
|
}
|
|
267
239
|
|
|
@@ -269,14 +241,14 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
|
|
|
269
241
|
_validateServiceInstanceName(serviceInstanceName) {
|
|
270
242
|
// valid service name chars: alpha-numeric, hyphens, and underscores
|
|
271
243
|
if (/[^\w-_]+/g.exec(serviceInstanceName)) {
|
|
272
|
-
throw
|
|
244
|
+
throw `Service name ${serviceInstanceName} must only contain alpha-numeric, hyphens, and underscores.`;
|
|
273
245
|
}
|
|
274
246
|
}
|
|
275
247
|
|
|
276
248
|
|
|
277
249
|
_injectTunnelAddress(serviceKey, tunnelAddress) {
|
|
278
250
|
if (!/\w+:\d+/.test(tunnelAddress)) {
|
|
279
|
-
throw
|
|
251
|
+
throw `Invalid tunnel address '${tunnelAddress}' - must be in form 'host:port'`;
|
|
280
252
|
}
|
|
281
253
|
const [tunnelHost, tunnelPort] = tunnelAddress.split(':')
|
|
282
254
|
const { host, port } = serviceKey
|
|
@@ -288,5 +260,3 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
|
|
|
288
260
|
return serviceKey
|
|
289
261
|
}
|
|
290
262
|
}
|
|
291
|
-
|
|
292
|
-
module.exports = new HanaDeployer();
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const util = require('util');
|
|
3
|
+
const { bold } = require('../../util/term');
|
|
3
4
|
|
|
4
5
|
const HDI_DEPLOY_LIB = '@sap/hdi-deploy';
|
|
5
6
|
|
|
6
|
-
class HdiDeployUtil {
|
|
7
|
+
module.exports = new class HdiDeployUtil {
|
|
7
8
|
|
|
8
|
-
async deploy(dbDir, vcapEnv = {},
|
|
9
|
+
async deploy(dbDir, vcapEnv = {}, { autoUndeploy } = {}) {
|
|
9
10
|
console.log(`Deploying to HANA from ${dbDir}`);
|
|
10
11
|
|
|
11
|
-
const
|
|
12
|
+
const { clean_env, deploy } = this._loadHdiDeployLib(dbDir);
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
const deployerEnv = JSON.parse(JSON.stringify(process.env));
|
|
14
15
|
const hdiDeployOptions = deployerEnv.HDI_DEPLOY_OPTIONS ? JSON.parse(deployerEnv.HDI_DEPLOY_OPTIONS) : {};
|
|
15
16
|
delete hdiDeployOptions.root;
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
deployerEnv = hdiDeployLib.clean_env(deployerEnv);
|
|
19
|
-
}
|
|
18
|
+
clean_env?.(deployerEnv);
|
|
20
19
|
|
|
21
20
|
if (vcapEnv.VCAP_SERVICES) {
|
|
22
21
|
deployerEnv.VCAP_SERVICES = JSON.stringify(vcapEnv.VCAP_SERVICES);
|
|
@@ -28,8 +27,8 @@ class HdiDeployUtil {
|
|
|
28
27
|
deployerEnv.TARGET_CONTAINER = vcapEnv.TARGET_CONTAINER;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
if (
|
|
32
|
-
console.log(`
|
|
30
|
+
if (autoUndeploy) {
|
|
31
|
+
console.log(`HDI deployer automatically undeploys deleted resources using --auto-undeploy.`);
|
|
33
32
|
hdiDeployOptions.auto_undeploy = true;
|
|
34
33
|
}
|
|
35
34
|
|
|
@@ -37,12 +36,12 @@ class HdiDeployUtil {
|
|
|
37
36
|
deployerEnv.HDI_DEPLOY_OPTIONS = JSON.stringify(hdiDeployOptions);
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
const asyncDeploy = util.promisify(
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
43
|
-
let message = `HDI deployment failed with exit code ${
|
|
44
|
-
if (
|
|
45
|
-
message += `. ${
|
|
39
|
+
const asyncDeploy = util.promisify(deploy);
|
|
40
|
+
const { exitCode, signal } = await asyncDeploy(dbDir, deployerEnv);
|
|
41
|
+
if (exitCode) {
|
|
42
|
+
let message = `HDI deployment failed with exit code ${exitCode}`;
|
|
43
|
+
if (signal) {
|
|
44
|
+
message += `. ${signal}`;
|
|
46
45
|
}
|
|
47
46
|
throw new Error(message);
|
|
48
47
|
}
|
|
@@ -55,7 +54,7 @@ class HdiDeployUtil {
|
|
|
55
54
|
if (!libPath) {
|
|
56
55
|
throw new Error(`Required library '${HDI_DEPLOY_LIB}' not found in
|
|
57
56
|
${searchPaths.join('\n ')}
|
|
58
|
-
Add it either
|
|
57
|
+
Add it either to ${bold('devDependencies')} using 'npm install -D ${HDI_DEPLOY_LIB}' or install it globally using 'npm install -g ${HDI_DEPLOY_LIB}'.`);
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
console.log(`Using HDI deployer from ${libPath}`)
|
|
@@ -64,5 +63,3 @@ Add it either as a devDependency using 'npm install -D ${HDI_DEPLOY_LIB}' or ins
|
|
|
64
63
|
return require(libPath);
|
|
65
64
|
}
|
|
66
65
|
}
|
|
67
|
-
|
|
68
|
-
module.exports = new HdiDeployUtil();
|
|
@@ -98,7 +98,7 @@ async function _getProjectInfo(projectPath, mta) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
async function _getMta(projectPath) {
|
|
101
|
-
// yaml.parse
|
|
101
|
+
// yaml.parse doesn't like null
|
|
102
102
|
const mtaFilePath = path.join(projectPath, MTA_YAML)
|
|
103
103
|
|
|
104
104
|
const existsMtaYaml = existsSync(mtaFilePath)
|
package/lib/import/common.js
CHANGED
|
@@ -148,30 +148,11 @@ async function _copyToSrvExternal(file, dst, cwd) {
|
|
|
148
148
|
return file;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
async function isValidJsonObject(options){
|
|
152
|
-
let config;
|
|
153
|
-
try {
|
|
154
|
-
if (options.config) {
|
|
155
|
-
config = JSON.parse(options.config);
|
|
156
|
-
return config;
|
|
157
|
-
}
|
|
158
|
-
} catch (error) {
|
|
159
|
-
throw new Error(messages.SPECIFY_CONFIG);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
151
|
async function preProcess(file, options, cwd) {
|
|
164
152
|
let srcFilePath = await isValidInputFile(path.resolve(cwd, file));
|
|
165
153
|
if (options.as && !_supportedAsFormats().includes(options.as))
|
|
166
154
|
throw new Error(messages.INVALID_AS_OPTION);
|
|
167
|
-
|
|
168
|
-
const external = path.resolve(cwd, 'srv/external');
|
|
169
|
-
const destFilePath = path.join(external, path.basename(srcFilePath));
|
|
170
|
-
|
|
171
|
-
if (!options.dry && !options.no_copy)
|
|
172
|
-
srcFilePath = await _copyToSrvExternal(srcFilePath, destFilePath, cwd);
|
|
173
|
-
|
|
174
|
-
return [srcFilePath, destFilePath];
|
|
155
|
+
return srcFilePath;
|
|
175
156
|
}
|
|
176
157
|
|
|
177
158
|
async function processOptions(cliOptions, odataOptions) {
|
|
@@ -359,14 +340,28 @@ async function _updatePackageJson(dest, services, kind, cwd, config) {
|
|
|
359
340
|
}
|
|
360
341
|
}
|
|
361
342
|
|
|
343
|
+
function _parseValidJsonConfig(config) {
|
|
344
|
+
try {
|
|
345
|
+
if (config) {
|
|
346
|
+
return JSON.parse(config);
|
|
347
|
+
}
|
|
348
|
+
} catch (error) {
|
|
349
|
+
throw new Error(messages.SPECIFY_CONFIG);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
362
353
|
async function postProcess(filePath, options, csn, cwd) {
|
|
363
354
|
const services = _getServiceNames(csn);
|
|
364
|
-
let config =
|
|
365
|
-
|
|
355
|
+
let config = _parseValidJsonConfig(options.config);
|
|
356
|
+
const external = path.resolve(cwd, 'srv/external');
|
|
357
|
+
const destFilePath = path.join(external, path.basename(filePath));
|
|
358
|
+
if (options.out) options.out = _validateAndComputeOutputFilePath(options.out, options.as, destFilePath);
|
|
366
359
|
const kind = options.inputFileKind;
|
|
367
|
-
const result = await _getResult(csn, options.as, options.out ||
|
|
360
|
+
const result = await _getResult(csn, options.as, options.out || destFilePath.replace(/\.[^.]+$/, ''), options.force, kind);
|
|
368
361
|
const written = options.dry || await _write(result, services, cwd);
|
|
369
362
|
const registered = options.dry || options.no_save || await _updatePackageJson(result[1].replace(/\.[^.]+$/, ''), services, kind, cwd, config);
|
|
363
|
+
if (!options.dry && !options.no_copy)
|
|
364
|
+
await _copyToSrvExternal(filePath, destFilePath, cwd);
|
|
370
365
|
if (options.dry) return console.log(result[0]);
|
|
371
366
|
return Promise.all([written, registered]);
|
|
372
367
|
}
|
|
@@ -1025,7 +1025,6 @@ MetadataConverter.Context = Context;
|
|
|
1025
1025
|
* @param {Object} result The metadata abstract syntax tree of the depended metadata document
|
|
1026
1026
|
*/
|
|
1027
1027
|
|
|
1028
|
-
|
|
1029
1028
|
/**
|
|
1030
1029
|
* This callback is displayed as part of the DefaultXmlStrategy class.
|
|
1031
1030
|
* This factory is called when the converter can not find a needed type within the current metadata document.
|
|
@@ -1073,7 +1072,6 @@ The result must be returned via 'return' statement.
|
|
|
1073
1072
|
* @returns {Expression} The expression to interpret the current element node
|
|
1074
1073
|
*/
|
|
1075
1074
|
|
|
1076
|
-
|
|
1077
1075
|
/**
|
|
1078
1076
|
* This callback is displayed as part of the DefaultXmlStrategy class.
|
|
1079
1077
|
* This callback is called on finishing a strategy.
|
|
@@ -1083,7 +1081,6 @@ The result must be returned via 'return' statement.
|
|
|
1083
1081
|
* @param {Any} result The output of the Strategy
|
|
1084
1082
|
*/
|
|
1085
1083
|
|
|
1086
|
-
|
|
1087
1084
|
class DefaultXmlStrategy extends Strategy {
|
|
1088
1085
|
constructor(options) {
|
|
1089
1086
|
super(undefined, options);
|
|
@@ -1243,7 +1240,6 @@ class DefaultXmlStrategy extends Strategy {
|
|
|
1243
1240
|
}
|
|
1244
1241
|
MetadataConverter.DefaultXmlStrategy = DefaultXmlStrategy;
|
|
1245
1242
|
|
|
1246
|
-
|
|
1247
1243
|
class EdmxRootExpression extends Expression {
|
|
1248
1244
|
constructor(element, expression, context) { super(element, expression, context); }
|
|
1249
1245
|
interpret() {
|
|
@@ -1441,7 +1437,6 @@ class AbstractLogicalExpression extends Expression {
|
|
|
1441
1437
|
}
|
|
1442
1438
|
}
|
|
1443
1439
|
|
|
1444
|
-
|
|
1445
1440
|
class AndExpression extends AbstractLogicalExpression {
|
|
1446
1441
|
constructor(element, expression, context) { super(element, expression, context, '$And'); }
|
|
1447
1442
|
}
|
|
@@ -1671,8 +1666,18 @@ class PropertyValueExpression extends Expression {
|
|
|
1671
1666
|
this.setTarget(target);
|
|
1672
1667
|
this.buildTypeCollectionAndFacets('$Type');
|
|
1673
1668
|
|
|
1669
|
+
/**
|
|
1670
|
+
* We can have instances of EDMX where we see value for a particular attribute as:
|
|
1671
|
+
* AnnotationPath="part/icipants/@UI.LineItem" or Path="SAP__Messages/random”, etc
|
|
1672
|
+
*
|
|
1673
|
+
* In such cases we need to replace '/' with '.' and only then the CSN generated will
|
|
1674
|
+
* be treated correctly in the later steps.
|
|
1675
|
+
* * For now this is only done for AnnotationPath value in #1680 to resolve an issue
|
|
1676
|
+
* * TODO: In future we might have to do it for many such value.
|
|
1677
|
+
*/
|
|
1674
1678
|
if (element.attributes.Path) target[name] = { "=": element.attributes.Path };
|
|
1675
1679
|
if (element.attributes.PropertyPath) target[name] = { "=": element.attributes.PropertyPath };
|
|
1680
|
+
if (element.attributes.AnnotationPath) target[name] = { "=": (element.attributes.AnnotationPath).replaceAll('/', '.') };
|
|
1676
1681
|
if (element.attributes.EnumMember) target[name] = { "#": element.attributes.EnumMember.substring(element.attributes.EnumMember.lastIndexOf('/') + 1) };
|
|
1677
1682
|
if (element.attributes.NavigationPropertyPath) target[name] = { "=": element.attributes.NavigationPropertyPath };
|
|
1678
1683
|
if (element.attributes.String) target[name] = element.attributes.String;
|
|
@@ -1684,8 +1689,8 @@ class PropertyValueExpression extends Expression {
|
|
|
1684
1689
|
if (element.attributes.Bool) target[name] = element.attributes.Bool === 'true';
|
|
1685
1690
|
/**
|
|
1686
1691
|
* If the property valus is of type `Date`, import it as a string itself
|
|
1687
|
-
* <PropertyValue Property="Date" Date="2021-08-24"
|
|
1688
|
-
* We might have to have a strict check in the
|
|
1692
|
+
* <PropertyValue Property="Date" Date="2021-08-24"/>
|
|
1693
|
+
* We might have to have a strict check in the future.. 'YYYY-MM_DD`
|
|
1689
1694
|
*/
|
|
1690
1695
|
if (element.attributes.Date) target[name] = element.attributes.Date;
|
|
1691
1696
|
|
|
@@ -2411,7 +2416,6 @@ class OnDeleteExpression extends Expression {
|
|
|
2411
2416
|
|
|
2412
2417
|
}
|
|
2413
2418
|
|
|
2414
|
-
|
|
2415
2419
|
MetadataConverter.createOdataV4MetadataXmlToOdataV4CsdlStrategy = (name_space) => {
|
|
2416
2420
|
MetadataConverter.custom_namespace = name_space;
|
|
2417
2421
|
return new MetadataConverter.DefaultXmlStrategy()
|
|
@@ -426,8 +426,8 @@ function _parseAnnotationTerms(annotationTerm, annotationTermObject, csnDefsTarg
|
|
|
426
426
|
function _resolveAnotationTerm(schemaDataAnnotation, parserContext) {
|
|
427
427
|
// Case 1: We have the Namespace and Alias for the vocabulary
|
|
428
428
|
for (const [alias, namespace] of Object.entries(parserContext.vocabAliasToNamespace)) {
|
|
429
|
-
if (schemaDataAnnotation.includes('@' + alias)) {
|
|
430
|
-
schemaDataAnnotation = schemaDataAnnotation.split('@' + alias).join('@' + known_vocabularies[namespace]);
|
|
429
|
+
if (schemaDataAnnotation.includes('@' + alias + '.')) {
|
|
430
|
+
schemaDataAnnotation = schemaDataAnnotation.split('@' + alias + '.').join('@' + known_vocabularies[namespace] + '.');
|
|
431
431
|
}
|
|
432
432
|
}
|
|
433
433
|
/**
|
|
@@ -1225,6 +1225,15 @@ function _addSuffixForCollisionTypes(fqName, suffix) {
|
|
|
1225
1225
|
function _parseEntityContainer(csnDefs, entityContainerElements, parserContext) {
|
|
1226
1226
|
const entityContainerElementNames = Object.keys(entityContainerElements);
|
|
1227
1227
|
const redundantElementNames = [];
|
|
1228
|
+
let possibleAnnotations = {};
|
|
1229
|
+
|
|
1230
|
+
// filter entries starting with '@', they can be possible annotation
|
|
1231
|
+
entityContainerElementNames.filter(
|
|
1232
|
+
elementName => elementName.startsWith('@')
|
|
1233
|
+
).forEach(elementName => {
|
|
1234
|
+
possibleAnnotations[elementName] = entityContainerElements[elementName];
|
|
1235
|
+
delete entityContainerElements[elementName];
|
|
1236
|
+
});
|
|
1228
1237
|
|
|
1229
1238
|
entityContainerElementNames.forEach(ecElement => {
|
|
1230
1239
|
if (entityContainerElements[ecElement] instanceof Object) {
|
|
@@ -1294,6 +1303,49 @@ function _parseEntityContainer(csnDefs, entityContainerElements, parserContext)
|
|
|
1294
1303
|
delete csnDefs[name];
|
|
1295
1304
|
});
|
|
1296
1305
|
|
|
1306
|
+
// check for possible annotations present within the entity container
|
|
1307
|
+
if (Object.keys(possibleAnnotations).length > 0) {
|
|
1308
|
+
let entityContainerAnnotations = {};
|
|
1309
|
+
let annotationTarget = parserContext.primarySchema + '.' + parserContext.entityContainerName;
|
|
1310
|
+
possibleAnnotations = _resolveAnotationTerm(
|
|
1311
|
+
JSON.stringify(possibleAnnotations), parserContext
|
|
1312
|
+
);
|
|
1313
|
+
/**
|
|
1314
|
+
* If the same annotation target already exists because of the annotation present outside
|
|
1315
|
+
* of the EntityContainer, then append the possible annotations to it and parse all of them.
|
|
1316
|
+
* Few exact same annotation term if exists, will result in information being overwritten
|
|
1317
|
+
* by the annotation term present within the EntityContainer.
|
|
1318
|
+
*/
|
|
1319
|
+
if (Object.keys(parserContext.entityContainerAnnotations).length > 0) {
|
|
1320
|
+
entityContainerAnnotations = parserContext.entityContainerAnnotations;
|
|
1321
|
+
/**
|
|
1322
|
+
* When the parserContext.entityContainerAnnotations contains the annotation target,
|
|
1323
|
+
* and we have unique information present within EntityContainer as well as indipendent
|
|
1324
|
+
* annotation, we have to merge them.
|
|
1325
|
+
*/
|
|
1326
|
+
if (parserContext.entityContainerAnnotations[annotationTarget] &&
|
|
1327
|
+
Object.keys(parserContext.entityContainerAnnotations[annotationTarget]).length > 0
|
|
1328
|
+
) {
|
|
1329
|
+
entityContainerAnnotations[annotationTarget] = Object.assign(
|
|
1330
|
+
entityContainerAnnotations[annotationTarget],
|
|
1331
|
+
possibleAnnotations
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* If the annotation target name is different, we can expand all of the information
|
|
1336
|
+
* into a single object itself
|
|
1337
|
+
*/
|
|
1338
|
+
else {
|
|
1339
|
+
entityContainerAnnotations[annotationTarget] = possibleAnnotations;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
// Simply add the possible annotations to the object with the target
|
|
1343
|
+
else {
|
|
1344
|
+
entityContainerAnnotations[annotationTarget] = possibleAnnotations;
|
|
1345
|
+
}
|
|
1346
|
+
parserContext.entityContainerAnnotations = entityContainerAnnotations;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1297
1349
|
// parse all the entity container annotations
|
|
1298
1350
|
if (parserContext.entityContainerAnnotations) {
|
|
1299
1351
|
_parseAnnotations(parserContext.entityContainerAnnotations, csnDefs, parserContext);
|
|
@@ -1450,6 +1502,17 @@ function _collectSchemaElements(edmxAsJson, schemaDataList, parserContext) {
|
|
|
1450
1502
|
|
|
1451
1503
|
_collectUnboundedActionFunction(schemaDataList, parserContext);
|
|
1452
1504
|
|
|
1505
|
+
// capture all the references
|
|
1506
|
+
if (edmxAsJson.$Reference) {
|
|
1507
|
+
Object.entries(edmxAsJson.$Reference).forEach(ref => {
|
|
1508
|
+
if (ref[1].$Include[0].$Alias &&
|
|
1509
|
+
known_vocabularies.hasOwnProperty(ref[1].$Include[0].$Namespace)
|
|
1510
|
+
) {
|
|
1511
|
+
parserContext.vocabAliasToNamespace[ref[1].$Include[0].$Alias] = ref[1].$Include[0].$Namespace;
|
|
1512
|
+
}
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1453
1516
|
// collect entityType, enumType, complexType, typeDefinition, bounded action/function, annotations
|
|
1454
1517
|
for (let schemaData of schemaDataList) {
|
|
1455
1518
|
let currNamespace = schemaData.$SchemaNamespace;
|
|
@@ -1472,15 +1535,6 @@ function _collectSchemaElements(edmxAsJson, schemaDataList, parserContext) {
|
|
|
1472
1535
|
}
|
|
1473
1536
|
// annotations
|
|
1474
1537
|
else if (elementName === '$Annotations') {
|
|
1475
|
-
if (edmxAsJson.$Reference) {
|
|
1476
|
-
Object.entries(edmxAsJson.$Reference).forEach(ref => {
|
|
1477
|
-
if (ref[1].$Include[0].$Alias &&
|
|
1478
|
-
known_vocabularies.hasOwnProperty(ref[1].$Include[0].$Namespace)
|
|
1479
|
-
) {
|
|
1480
|
-
parserContext.vocabAliasToNamespace[ref[1].$Include[0].$Alias] = ref[1].$Include[0].$Namespace;
|
|
1481
|
-
}
|
|
1482
|
-
});
|
|
1483
|
-
}
|
|
1484
1538
|
schemaData.$Annotations = _resolveAnotationTerm(JSON.stringify(schemaData.$Annotations), parserContext);
|
|
1485
1539
|
/**
|
|
1486
1540
|
* collect entity container annotations with the assumption that
|
|
@@ -1552,7 +1606,8 @@ function _preProcess(edmxAsJson, parserContext, importerContext) {
|
|
|
1552
1606
|
parserContext.entityToEntitySetMap._.size === 0 &&
|
|
1553
1607
|
parserContext.entityToSingletonMap._.size === 0 &&
|
|
1554
1608
|
parserContext.actionToActionImportMap._.size === 0 &&
|
|
1555
|
-
parserContext.functionToFunctionImportMap._.size === 0)
|
|
1609
|
+
parserContext.functionToFunctionImportMap._.size === 0)
|
|
1610
|
+
{
|
|
1556
1611
|
parserContext.mockServerUc = false;
|
|
1557
1612
|
}
|
|
1558
1613
|
|
package/lib/init/constants.js
CHANGED
|
@@ -57,9 +57,7 @@ module.exports = {
|
|
|
57
57
|
SAMPLES: 'https://github.com/SAP-samples/cloud-cap-samples',
|
|
58
58
|
CAPIRE: 'https://cap.cloud.sap',
|
|
59
59
|
MAVEN_ARCHETYPE_HELP: `https://cap.cloud.sap/docs/java/development/#the-maven-archetype`,
|
|
60
|
-
MAVEN_INSTALL_HELP: `https://maven.apache.org/install.html
|
|
61
|
-
VSCODE: 'https://code.visualstudio.com/',
|
|
62
|
-
VSCODE_ESLINT: 'https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint'
|
|
60
|
+
MAVEN_INSTALL_HELP: `https://maven.apache.org/install.html`
|
|
63
61
|
}),
|
|
64
62
|
|
|
65
63
|
PROJECT_FILES: ['package.json', 'pom.xml', '.cdsrc.json']
|
package/lib/lint/Linter.js
CHANGED
|
@@ -38,7 +38,7 @@ class Linter {
|
|
|
38
38
|
/**
|
|
39
39
|
* Initializes `cds lint` call and generates the required content
|
|
40
40
|
* object for `cds` executable
|
|
41
|
-
* @returns object
|
|
41
|
+
* @returns object containing help, options, flags and shortcuts
|
|
42
42
|
*/
|
|
43
43
|
init(help = "", options = [], flags = [], shortcuts = []) {
|
|
44
44
|
this.eslintCmd = checks.resolveEslint(this.cdsdkPath, this.globalPath);
|
|
@@ -56,7 +56,7 @@ class Linter {
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Runner for 'cds lint' which is a wrapper for eslint cmd calls
|
|
59
|
-
* that
|
|
59
|
+
* that detects and adds the required cmd line arguments
|
|
60
60
|
* @param {*} args files/globs for eslint to lint
|
|
61
61
|
* @param {*} options options/flag passed by user
|
|
62
62
|
*/
|