@sap/cds-dk 4.3.3 → 4.5.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +62 -1
  2. package/bin/activate.js +1 -2
  3. package/bin/deploy/index.js +1 -1
  4. package/bin/deploy/to-sqlite.js +2 -2
  5. package/bin/extend.js +2 -4
  6. package/bin/help.js +1 -1
  7. package/bin/login.js +7 -8
  8. package/bin/logout.js +1 -2
  9. package/bin/repl.js +1 -1
  10. package/bin/watch.js +12 -8
  11. package/lib/client/activate.js +243 -0
  12. package/lib/client/auth_manager.js +115 -0
  13. package/lib/client/base_command.js +55 -0
  14. package/lib/client/extend.js +179 -0
  15. package/lib/client/helper/cf_helper.js +186 -0
  16. package/lib/client/helper/dependency_helper.js +26 -0
  17. package/lib/client/helper/env_helper.js +4 -0
  18. package/lib/client/helper/fs_helper.js +83 -0
  19. package/lib/client/helper/logging.js +37 -0
  20. package/lib/client/helper/question.js +50 -0
  21. package/lib/client/helper/string_helper.js +53 -0
  22. package/lib/client/helper/url_helper.js +17 -0
  23. package/lib/client/index.js +14 -0
  24. package/lib/client/params.js +435 -0
  25. package/lib/client/settings_manager.js +526 -0
  26. package/lib/import/index.js +17 -38
  27. package/lib/import/message.js +6 -7
  28. package/lib/import/v2parser.js +340 -64
  29. package/lib/import/v4parser.js +346 -59
  30. package/lib/init/constants.js +1 -1
  31. package/lib/init/index.js +13 -10
  32. package/lib/init/template/data/index.js +38 -46
  33. package/lib/init/template/hana/index.js +2 -2
  34. package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/fixed/schema.cds +4 -0
  35. package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/invalid/schema.cds +4 -0
  36. package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/test.js +10 -0
  37. package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/valid/schema.cds +6 -0
  38. package/lib/init/template/lint/index.js +8 -6
  39. package/lib/init/template/mta/projectReader.js +8 -5
  40. package/lib/init/template/nodejs/files/.vscode/extensions.json +1 -1
  41. package/lib/init/template/nodejs/index.js +1 -8
  42. package/lib/init/util/logger.js +1 -1
  43. package/lib/lint/Linter.js +82 -65
  44. package/lib/lint/io.js +9 -33
  45. package/npm-shrinkwrap.json +6327 -11301
  46. package/package.json +6 -7
  47. package/lib/init/template/lint/files/.eslint/tests/no-entity-moo.test.js +0 -34
@@ -0,0 +1,526 @@
1
+ const os = require('os');
2
+ const path = require('path');
3
+ const { fse } = require('@sap/cds-foss');
4
+
5
+ const requireGlobal = require('./helper/dependency_helper');
6
+ const keytar = requireGlobal('keytar');
7
+
8
+ const { getAppFromSuggestions, getAppName, getSubdomain } = require('./helper/cf_helper');
9
+ const isBas = require('./helper/env_helper');
10
+ const Logger = require('./helper/logging');
11
+ const Question = require('./helper/question');
12
+ const { defaultSchema, extractSubdomain, schemaRegex } = require('./helper/url_helper');
13
+ const { ParamCollection, Persistence } = require('./params');
14
+
15
+ const CONFIG_SUBDIRS = {
16
+ linux: '.config',
17
+ darwin: path.join('Library', 'Preferences'),
18
+ win32: 'AppData'
19
+ }
20
+
21
+ const TOKEN_STORAGE_DESC = {
22
+ plain: 'plain-text storage',
23
+ keyring: 'keyring'
24
+ }
25
+
26
+ const KEYRING_DESC = {
27
+ linux: 'libsecret',
28
+ darwin: 'Keychain',
29
+ win32: 'Credential Vault'
30
+ }
31
+
32
+ const MTX_FULLY_QUALIFIED = 'com.sap.cds.mtx';
33
+ const OAUTH_PATH = '/mtx/v1/oauth/token';
34
+
35
+ const SETTINGS_DIR = path.join(os.homedir(), CONFIG_SUBDIRS[os.platform()] || '', MTX_FULLY_QUALIFIED);
36
+ const SETTINGS_FILE = 'projects.json';
37
+ const AUTH_FILE = 'auth.json';
38
+
39
+ const CONFIG = {
40
+ paths: {
41
+ settings: path.join(SETTINGS_DIR, SETTINGS_FILE),
42
+ auth: path.join(SETTINGS_DIR, AUTH_FILE)
43
+ },
44
+ keyringDesignation: KEYRING_DESC[os.platform()] || 'not supported'
45
+ };
46
+
47
+ function other(tokenStorage) {
48
+ if (!tokenStorage) {
49
+ return tokenStorage;
50
+ }
51
+ return tokenStorage === 'plain'
52
+ ? 'keyring'
53
+ : 'plain';
54
+ }
55
+
56
+ function getProjectFolder(params) {
57
+ const relativePath = params.get('projectFolder') || params.get('directory') || (
58
+ params.has('subdomain') && fse.existsSync(path.resolve(params.get('subdomain')))
59
+ ? params.get('subdomain')
60
+ : '.'
61
+ );
62
+ return fse.realpathSync(relativePath);
63
+ }
64
+
65
+ async function getAppUrlAndSubdomainFromSuggestions(params) {
66
+ const app = await getAppFromSuggestions();
67
+ if (!app) {
68
+ return;
69
+ }
70
+
71
+ params.set('appUrl', app.url);
72
+
73
+ if (!params.has('subdomain')) {
74
+ params.set('subdomain', await getSubdomain(app.name));
75
+ if (params.has('subdomain')) {
76
+ Logger.log(`Subdomain determined from CF app environment: ${params.get('subdomain')}`);
77
+ } else {
78
+ Logger.log('Failed to determine subdomain from CF app environment');
79
+ }
80
+ }
81
+ }
82
+
83
+ function getTokenBaseUrl(params, renewUrl) {
84
+ if (params.has('tokenUrl') && !renewUrl) {
85
+ return params.get('tokenUrl');
86
+ }
87
+ if (!params.has('appUrl')) {
88
+ throw new Error('Failed to determine token URL: app URL not given');
89
+ }
90
+ const baseUrl = new URL(params.get('appUrl'));
91
+ baseUrl.pathname = baseUrl.pathname.replace(/\/?$/, OAUTH_PATH);
92
+ return baseUrl.toString();
93
+ }
94
+
95
+ function getKeyringAccountName(params) {
96
+ return `${params.get('appUrl')}|${params.get('subdomain')}`;
97
+ }
98
+
99
+
100
+ function logConfigPaths() {
101
+ Logger.debug(`Settings are stored in ${CONFIG.paths.settings}`);
102
+ Logger.debug(`Authentication data is stored in ${CONFIG.paths.auth} or keyring ('${CONFIG.keyringDesignation}' on this platform)`);
103
+ }
104
+
105
+ function notifyLoggedIn(params) {
106
+ return params.get('renewLogin')
107
+ ? Logger.log.bind(Logger)
108
+ : Logger.debug.bind(Logger);
109
+ }
110
+
111
+ function emptyData(all) {
112
+ return all ? {} : new ParamCollection();
113
+ }
114
+
115
+ class SettingsManager {
116
+
117
+ static init() {
118
+ logConfigPaths();
119
+ }
120
+
121
+ static get config() {
122
+ return CONFIG;
123
+ }
124
+
125
+ // Call with 'projectFolder' param as a resolved path.
126
+ static async saveSettings(params) {
127
+ Logger.debug(`Saving settings for project ${params.get('projectFolder')}`);
128
+ if (params.has('token') && ! params.has('tokenStorage')) {
129
+ Logger.log('Note: the authentication token is not saved by default. To save the token for later commands, please run `cds login`.');
130
+ }
131
+
132
+ await this._saveToFile(params.get('projectFolder'), params.toValueMap(Persistence.setting));
133
+
134
+ if (!params.has('token')) {
135
+ return;
136
+ }
137
+
138
+ if (params.get('clearOtherTokenStorage')) {
139
+ // Delete token from the other storage.
140
+ params.delete('clearOtherTokenStorage');
141
+ await this.deleteToken(params, other(params.get('tokenStorage')));
142
+ }
143
+
144
+ if (params.get('tokenStorage') === 'plain') {
145
+ await this._saveAuthToFile(params, params.toValueMap(Persistence.auth));
146
+ } else if (params.get('tokenStorage') === 'keyring') {
147
+ await this._saveAuthToKeyring(params, params.toValueMap(Persistence.auth));
148
+ }
149
+ }
150
+
151
+ static async loadAndMergeSettings(params, logout = false) {
152
+ const projectFolder = getProjectFolder(params);
153
+ params.set('projectFolder', projectFolder);
154
+
155
+ Logger.debug(`Loading settings for project ${projectFolder}`);
156
+ const loadedSettings = await this._loadFromFile(projectFolder);
157
+ Logger.debug(`Loaded project settings: ${loadedSettings.format()}`);
158
+
159
+ params.merge(loadedSettings);
160
+
161
+ await this.updateUrls(params, logout);
162
+ await this.checkKeytar(params, logout);
163
+ await this.addAuth(params, logout);
164
+ Logger.debug(`Effective project settings: ${params.format()}`);
165
+ }
166
+
167
+ static async updateUrls(params, logout) {
168
+ async function addAppUrlAndSubdomain() {
169
+ if (params.has('appUrl') && !schemaRegex.test(params.get('appUrl'))) {
170
+ params.set('appUrl', defaultSchema + params.get('appUrl'));
171
+ Logger.debug(`Added schema to app URL: ${params.get('appUrl')}`);
172
+ }
173
+ if (!logout) {
174
+ if (!params.has('appUrl')) {
175
+ await getAppUrlAndSubdomainFromSuggestions(params);
176
+ } else if (!params.has('subdomain')) {
177
+ Logger.debug('Subdomain not given');
178
+ const appName = await getAppName(params.get('appUrl'));
179
+ if (appName) {
180
+ params.set('subdomain', await getSubdomain(appName));
181
+ }
182
+ }
183
+ }
184
+ if (!params.has('appUrl')) {
185
+ throw new Error('App URL not given. Please specify it or log in to Cloud Foundry and repeat this command.');
186
+ }
187
+ if (!params.has('subdomain')) {
188
+ params.set('subdomain', extractSubdomain(params.get('appUrl')));
189
+ if (!params.has('subdomain')) {
190
+ throw new Error('Subdomain not given');
191
+ }
192
+ }
193
+ }
194
+ function renewTokenUrl() {
195
+ const renewUrl = params.get('appUrl') !== params.get('appUrl');
196
+ if (!params.get('tokenUrl') || renewUrl) {
197
+ params.set('tokenUrl', getTokenBaseUrl(params, renewUrl));
198
+ }
199
+ }
200
+
201
+ await addAppUrlAndSubdomain();
202
+
203
+ if (!logout) {
204
+ renewTokenUrl();
205
+ }
206
+ }
207
+
208
+ static async checkKeytar(params, logout) {
209
+ if (isBas()) {
210
+ if (params.get('tokenStorage') === 'keyring') {
211
+ Logger.log(`NOTE: Keyring storage not supported on SAP Business Application Studio. Switching to plain-text storage.`);
212
+ params.set('tokenStorage', 'plain');
213
+ }
214
+ return;
215
+ }
216
+ if (params.get('username') || keytar) {
217
+ return;
218
+ }
219
+ if (params.get('tokenStorage') === 'keyring') {
220
+ throw new Error('Keyring storage requested but keytar not installed. Please run `npm install -g keytar` or ' +
221
+ 'switch to plain-text storage by adding `--plain` (discouraged).');
222
+ }
223
+ if (params.get('renewLogin') || logout) {
224
+ Logger.log('Disabling keyring functionality: \'keytar\' module not found. Run `npm install -g keytar` to install it.');
225
+ }
226
+ }
227
+
228
+ static async addAuth(params, logout) {
229
+ async function addPassword() {
230
+ params.set('password', await Question.askQuestion('Password: ', undefined, true));
231
+ if (params.get('password') === '') {
232
+ throw new Error('Password cannot be empty');
233
+ }
234
+ }
235
+ async function addClientSecret() {
236
+ params.set('clientsecret', await Question.askQuestion('clientsecret: ', undefined, true));
237
+ if (params.get('clientsecret') === '') {
238
+ throw new Error('clientsecret cannot be empty');
239
+ }
240
+ }
241
+ async function addPasscode() {
242
+ const prompt = `Passcode${params.get('passcodeUrl') ? ' (visit ' + params.get('passcodeUrl') + ' to generate)' : ''}: `;
243
+ params.set('passcode', await Question.askQuestion(prompt, undefined, true));
244
+ console.log(); // DON'T: process.stdout.write('\n');
245
+ if (params.get('passcode') === '') {
246
+ throw new Error('Passcode cannot be empty');
247
+ }
248
+ }
249
+ function setRelevantAuth(auth) {
250
+ if (!(auth.keyring.has('tokenStorage') || auth.plain.has('tokenStorage'))) {
251
+ // Saved auth data not present.
252
+ return;
253
+ }
254
+ let storage = params.get('tokenStorage');
255
+ if (auth.keyring.has('tokenStorage') && auth.plain.has('tokenStorage')) {
256
+ // Both storage places contain data: retrieve from selected or keyring.
257
+ storage = storage || 'keyring';
258
+ Logger.debug('WARNING: authentication data found in both kinds of storage. ' +
259
+ `Using data from ${TOKEN_STORAGE_DESC[storage]}; other storage will be cleared when next saving.`);
260
+
261
+ params.merge(auth[storage]);
262
+ params.set('clearOtherTokenStorage', true);
263
+
264
+ } else if (storage && auth[other(storage)].has('tokenStorage')) {
265
+ // Selected storage contains no data, but other one does: retrieve from other storage, but earmark migration to selected one.
266
+ Logger.debug(`Using authentication data from ${TOKEN_STORAGE_DESC[other(storage)]}; will be migrated to other storage on save.`);
267
+
268
+ params.merge(auth[other(storage)]);
269
+ params.set('tokenStorage', storage);
270
+ params.set('clearOtherTokenStorage', true);
271
+
272
+ } else {
273
+ // One storage contains data, and there's no conflict with selection.
274
+ const storage = auth.keyring.get('tokenStorage') || auth.plain.get('tokenStorage');
275
+ Logger.debug(`Using authentication data from ${TOKEN_STORAGE_DESC[storage]}.`);
276
+
277
+ params.merge(auth[storage]);
278
+ }
279
+ }
280
+
281
+ Logger.debug(`Consolidating authentication data for project ${params.get('projectFolder')}`);
282
+
283
+ if (params.has('username')) {
284
+ if (!params.get('password') && !logout) {
285
+ await addPassword();
286
+ }
287
+ return;
288
+ }
289
+
290
+ if (params.get('clientid') && !params.get('clientsecret') && !logout) {
291
+ await addClientSecret();
292
+ }
293
+
294
+ const auth = {
295
+ plain: await this._loadAuthFromFile(params.get('appUrl'), params.get('subdomain')),
296
+ keyring: await this._loadAuthFromKeyring(params.get('appUrl'), params.get('subdomain'))
297
+ };
298
+
299
+ setRelevantAuth(auth);
300
+
301
+ if (!params.has('token') && !params.has('passcode') && !logout) {
302
+ await addPasscode();
303
+ }
304
+ }
305
+
306
+ static async deleteToken(params, fromStorage) {
307
+ const allParams = params.clone();
308
+ await this.loadAndMergeSettings(allParams, true);
309
+
310
+ const target = `URL ${allParams.get('appUrl')}, subdomain '${allParams.get('subdomain')}'`;
311
+ Logger.debug(`Deleting authentication data for ${target}`);
312
+
313
+ const deleteBoth = !fromStorage && allParams.get('tokenStorage') && allParams.get('clearOtherTokenStorage');
314
+
315
+ fromStorage = fromStorage || allParams.get('tokenStorage');
316
+ if (!fromStorage) {
317
+ Logger.log(`No authentication data found for ${target}`);
318
+ return;
319
+ }
320
+
321
+ if (fromStorage === 'plain' || deleteBoth) {
322
+ await this._saveAuthToFile(allParams, null);
323
+ }
324
+ if (fromStorage === 'keyring' || deleteBoth) {
325
+ await this._saveAuthToKeyring(allParams, null);
326
+ }
327
+ }
328
+
329
+ static async deleteSettingsWithoutToken(params) {
330
+ const projectFolder = getProjectFolder(params);
331
+ Logger.debug(`Deleting settings for project ${projectFolder}`);
332
+ await this._saveToFile(projectFolder, null);
333
+ }
334
+
335
+ static async deleteInvalid() {
336
+ Logger.log('Deleting invalid project settings');
337
+
338
+ const settingsByFolder = await this._loadFromFile(undefined);
339
+ const deletionFolders = [];
340
+ const deletionUrlsAndSubdomains = new Set();
341
+
342
+ Object.entries(settingsByFolder)
343
+ .filter(entry => ! fse.pathExistsSync(entry[0]))
344
+ .forEach(entry => {
345
+ delete settingsByFolder[entry[0]];
346
+ deletionFolders.push(entry[0]);
347
+ deletionUrlsAndSubdomains.add(entry[1].appUrl + '|' + entry[1].subdomain);
348
+ });
349
+
350
+ await this._saveAllSettingsToFile(settingsByFolder);
351
+
352
+ if (deletionFolders.length) {
353
+ for (const urlAndSubdomain of deletionUrlsAndSubdomains.values()) {
354
+ const appUrl = urlAndSubdomain.replace(/\|.*/, '');
355
+ const subdomain = urlAndSubdomain.replace(/.*\|/, '');
356
+ const urlReference = Object.values(settingsByFolder).find(settings => settings.appUrl === appUrl && settings.subdomain === subdomain);
357
+ if (!urlReference) {
358
+ await this.deleteToken(new ParamCollection({ appUrl, subdomain }));
359
+ }
360
+ }
361
+ Logger.log(`Deleted settings for nonexistent project folders:${deletionFolders.map(folder => ' ' + folder)}`);
362
+ } else {
363
+ Logger.log('All settings seem valid');
364
+ }
365
+ }
366
+
367
+ static async _saveToFile(projectFolder, paramValues) {
368
+ const paramValuesByFolder = await this._loadFromFile(undefined);
369
+ if (paramValues !== null) {
370
+ paramValuesByFolder[projectFolder] = paramValues;
371
+ await this._saveAllSettingsToFile(paramValuesByFolder);
372
+ Logger.debug(`Saved project settings: ${JSON.stringify(paramValues)}`);
373
+ } else {
374
+ delete paramValuesByFolder[projectFolder];
375
+ await this._saveAllSettingsToFile(paramValuesByFolder);
376
+ Logger.log('Deleted project settings');
377
+ }
378
+ }
379
+
380
+ static async _saveAllSettingsToFile(paramValuesByFolder) {
381
+ Logger.debug(`Saving all settings to ${CONFIG.paths.settings}`);
382
+ await fse.outputJson(CONFIG.paths.settings, paramValuesByFolder, { spaces: 2 });
383
+ Logger.debug('Saved settings');
384
+ }
385
+
386
+ static async _saveAuthToFile(params, authValues) {
387
+ Logger.debug(`Updating authentication data to ${CONFIG.paths.auth}`);
388
+ const appUrl = params.get('appUrl');
389
+ const location = `plain-text storage for app URL ${appUrl}, subdomain ${params.get('subdomain')}`;
390
+
391
+ const allAuthValues = await this._loadAuthFromFile(undefined, undefined);
392
+ if (authValues !== null) {
393
+ (allAuthValues[appUrl] || (allAuthValues[appUrl] = {}))[params.get('subdomain')] = authValues;
394
+ await fse.outputJson(CONFIG.paths.auth, allAuthValues, { spaces: 2 });
395
+ notifyLoggedIn(params)(`Saved authentication data to ${location}`);
396
+ } else if (allAuthValues[appUrl]) {
397
+ delete allAuthValues[appUrl][params.get('subdomain')];
398
+ if (Object.keys(allAuthValues[appUrl]).length === 0) {
399
+ delete allAuthValues[appUrl];
400
+ }
401
+ await fse.outputJson(CONFIG.paths.auth, allAuthValues, { spaces: 2 });
402
+ Logger.log(`Deleted authentication data from ${location}`);
403
+ } else {
404
+ Logger.log(`No authentication data to delete from ${location}`);
405
+ }
406
+ }
407
+
408
+ static async _saveAuthToKeyring(params, auth) {
409
+ if (!keytar) {
410
+ return;
411
+ }
412
+
413
+ Logger.debug('Updating authentication data to keyring');
414
+ const location = `keyring for app URL ${params.get('appUrl')}, subdomain ${params.get('subdomain')}`;
415
+
416
+ if (auth !== null) {
417
+ await keytar.setPassword(MTX_FULLY_QUALIFIED, getKeyringAccountName(params), JSON.stringify(auth));
418
+ notifyLoggedIn(params)(`Saved authentication data to ${location}`);
419
+ } else {
420
+ await keytar.deletePassword(MTX_FULLY_QUALIFIED, getKeyringAccountName(params));
421
+ Logger.log(`Deleted authentication data from ${location}`);
422
+ }
423
+ }
424
+
425
+ static async _loadFromFile(projectFolder) {
426
+ const all = !projectFolder;
427
+
428
+ if (!fse.existsSync(CONFIG.paths.settings)) {
429
+ Logger.debug('Settings file absent');
430
+ return emptyData(all);
431
+ }
432
+
433
+ Logger.debug('Reading settings file');
434
+ const settingsByFolder = await fse.readJson(CONFIG.paths.settings);
435
+
436
+ if (!settingsByFolder) {
437
+ Logger.debug('Empty settings file');
438
+ return emptyData(all);
439
+ }
440
+
441
+ if (all) { // return settings for all projects
442
+ return settingsByFolder;
443
+ }
444
+
445
+ if (!settingsByFolder[projectFolder]) {
446
+ Logger.debug(`No settings found for project ${projectFolder}`);
447
+ return new ParamCollection();
448
+ }
449
+
450
+ return new ParamCollection(settingsByFolder[projectFolder]);
451
+ }
452
+
453
+ static async _loadAuthFromFile(appUrl, subdomain) {
454
+ const all = !appUrl;
455
+
456
+ if (!fse.existsSync(CONFIG.paths.auth)) {
457
+ Logger.debug('Authentication-data file absent');
458
+ return emptyData(all);
459
+ }
460
+
461
+ Logger.debug(`Reading authentication data from ${CONFIG.paths.auth}`);
462
+ const allAuth = await fse.readJson(CONFIG.paths.auth);
463
+
464
+ if (!allAuth) {
465
+ Logger.debug('Empty authentication data');
466
+ return emptyData(all);
467
+ }
468
+
469
+ if (all) { // return auth data for all projects
470
+ return allAuth;
471
+ }
472
+
473
+ if (!allAuth[appUrl]) {
474
+ Logger.debug(`No authentication data found for app URL ${appUrl}`);
475
+ return new ParamCollection();
476
+ }
477
+
478
+ const authForSubdomain = allAuth[appUrl][subdomain];
479
+
480
+ if (!authForSubdomain) {
481
+ Logger.debug(`No authentication data found for subdomain ${subdomain}`);
482
+ return new ParamCollection();
483
+ }
484
+
485
+ if (authForSubdomain.token) {
486
+ authForSubdomain.tokenStorage = 'plain';
487
+ }
488
+
489
+ return new ParamCollection(authForSubdomain);
490
+ }
491
+
492
+ static async _loadAuthFromKeyring(appUrl, subdomain) {
493
+ if (!keytar) {
494
+ return new ParamCollection();
495
+ }
496
+
497
+ Logger.debug('Reading authentication data from keyring');
498
+
499
+ const authString = await keytar.getPassword(MTX_FULLY_QUALIFIED, getKeyringAccountName(new ParamCollection({ appUrl, subdomain })));
500
+
501
+ if (!authString) {
502
+ Logger.debug(`No authentication data found for URL ${appUrl} and subdomain '${subdomain}'`);
503
+ return new ParamCollection();
504
+ }
505
+
506
+ let auth;
507
+
508
+ try {
509
+ auth = JSON.parse(authString);
510
+ } catch (error) {
511
+ auth = {};
512
+ }
513
+
514
+ if (auth.token) {
515
+ auth.tokenStorage = 'keyring';
516
+ }
517
+
518
+ return new ParamCollection(auth);
519
+ }
520
+
521
+ }
522
+
523
+ module.exports = {
524
+ SettingsManager,
525
+ other
526
+ };
@@ -1,22 +1,15 @@
1
- // Command to invoke from: C:\D\edmx2csn\core\bin>
2
- // a) edmx2csn -i ../../input/metadata.xml -o ../../output/
3
- // b) node edmx2csn.js -i ../../input/metadata.xml -o ../../output/
4
1
 
5
2
  "use strict";
6
3
  let convert = require('xml-js');
7
4
  let axios = require('axios');
8
5
  let edmxv2CSN = require("./v2parser");
9
6
  let edmxv4CSN = require("./v4parser");
10
- let getMessages = require("./message");
7
+ const messages = require("./message").getMessages();
11
8
  let common = require("./common");
12
9
  const cds = require('@sap/cds');
13
10
  const fs = cds.utils, { path } = fs;
14
11
  let defaultFileName;
15
12
 
16
- function getV2Messages() {
17
- return getMessages();
18
- }
19
-
20
13
  async function _isValidXML(text) {
21
14
  let isValid = false;
22
15
  let cjson;
@@ -38,12 +31,13 @@ async function isValidInputFile(inputFileLoc) {
38
31
  const fileResolved = await fs.isfile(inputFileLoc);
39
32
 
40
33
  if (!fileResolved) {
41
- throw new Error(getMessages().SPECIFY_INPUT_FILE);
34
+ throw new Error(messages.SPECIFY_INPUT_FILE);
42
35
  }
43
36
  else if (inputFileLoc.endsWith(".edmx") || inputFileLoc.endsWith(".xml")) {
44
37
  file = fileResolved;
38
+ defaultFileName = file;
45
39
  } else {
46
- throw new Error(getMessages().INVALID_INPUT_FILE);
40
+ throw new Error(messages.INVALID_INPUT_FILE);
47
41
  }
48
42
  return file;
49
43
  }
@@ -56,7 +50,7 @@ function validateAndComputeOutputFilePath(outputFileLoc) {
56
50
  }
57
51
  if (outputFileLoc === true || // if output file is not mentioned
58
52
  outputFileLoc.trim() === "") {
59
- throw new Error(getV2Messages().SPECIFY_OUTPUT_FILE);
53
+ throw new Error(messages.SPECIFY_OUTPUT_FILE);
60
54
  } else if (outputFileLoc.endsWith(".json") || outputFileLoc.endsWith(".csn")) {
61
55
  filePath = outputFileLoc;
62
56
  } else if (index > 0 &&
@@ -65,19 +59,16 @@ function validateAndComputeOutputFilePath(outputFileLoc) {
65
59
  extension !== 'json' &&
66
60
  extension !== 'csn') {
67
61
  // Output file extension is not either .json or .csn ("test.abcde", "./srv/external/test.abcde")
68
- throw new Error(getV2Messages().INVALID_OUTPUT_FILE);
62
+ throw new Error(messages.INVALID_OUTPUT_FILE);
69
63
  } else if (!outputFileLoc.endsWith(".json") &&
70
64
  !outputFileLoc.endsWith(".csn") &&
71
65
  !outputFileLoc.endsWith("\\") &&
72
66
  !outputFileLoc.endsWith("/")) {
73
67
  // Output file extension is not mentioned ("./srv/external/test", "test", "c:\windows\output\test")
74
68
  filePath = outputFileLoc + ".csn";
75
- } else { // File name not specified ("./srv/external/", "c:\windows\output\test\")
76
- if (outputFileLoc.endsWith("/") || outputFileLoc.endsWith("\\")) {
77
- filePath = outputFileLoc + _getDefaultFileName();
78
- } else {
79
- filePath = outputFileLoc + "/" + _getDefaultFileName();
80
- }
69
+ } else if (outputFileLoc.endsWith("/") || outputFileLoc.endsWith("\\")){
70
+ // File name not specified ("./srv/external/", "c:\windows\output\test\")
71
+ filePath = outputFileLoc + path.parse(defaultFileName).name + ".csn";
81
72
  }
82
73
 
83
74
  return filePath;
@@ -96,7 +87,7 @@ async function getMetadataFromURL(url) {
96
87
  if (edmx && (await _isValidXML(edmx))) {
97
88
  return edmx;
98
89
  } else {
99
- throw new Error(getV2Messages().INVALID_EDMX_METADATA);
90
+ throw new Error(messages.INVALID_EDMX_METADATA);
100
91
  }
101
92
  }
102
93
 
@@ -135,21 +126,21 @@ async function edmx2csn(edmx, filename, context) {
135
126
  const result = edmx.match(/EntityType="([^.]+)/);
136
127
  const multipleSchemas = /(\s*<\/Schema>\s*<Schema [^>]*>)\s*/m;
137
128
  if (!result) {
138
- throw new Error(getV2Messages().MISSING_ENTITIES);
129
+ throw new Error(messages.MISSING_ENTITIES);
139
130
  }
140
131
  const namespace = result[1];
141
132
 
142
133
  if (multipleSchemas.test(edmx)) {
143
134
  edmx = _convertEDMXWithMultipleSchemas(edmx, multipleSchemas, namespace);
144
135
  }
145
- if (filename) {
146
- // Replace namespace by given filename...
136
+ let csn = await generateCSN(edmx, false, true, context);
137
+ if (filename) {
138
+ // Replace namespace by given filename in the csn file instead of edmx file
147
139
  const re = RegExp(`([("/])${namespace}([/").])`, 'g');
148
140
  const fn = `$1${path.parse(filename).name}$2`;
149
- edmx = edmx.replace(re, fn);
141
+ csn = csn.replace(re, fn);
150
142
  }
151
- const csn = JSON.parse(await generateCSN(edmx, false, true, context));
152
- return csn;
143
+ return JSON.parse(csn);
153
144
  }
154
145
 
155
146
  async function _generateEDMX2JSON(edmx) {
@@ -191,27 +182,15 @@ async function generateCSN(edmx, ignorePersistenceSkip, mockServerUc, context) {
191
182
  return edmxv4CSN.getEdmxv4CSN(edmx, ignorePersistenceSkip, mockServerUc);
192
183
  }
193
184
  return new Promise(function invalidFile(resolve, reject) {
194
- reject(new Error(getV2Messages().INVALID_EDMX_METADATA));
185
+ reject(new Error(messages.INVALID_EDMX_METADATA));
195
186
  });
196
187
  }
197
188
 
198
- function _getDefaultFileName() {
199
- let nameSpace;
200
- if (defaultFileName === undefined) {
201
- nameSpace = edmxv2CSN.getNamespace();
202
- if (nameSpace) {
203
- defaultFileName = nameSpace + ".csn"; //".json";
204
- }
205
- }
206
- return defaultFileName;
207
- }
208
-
209
189
  async function saveCSNModel(csnDataModel, fileLoc) {
210
190
  await fs.write(fileLoc, csnDataModel, "utf-8");
211
191
  }
212
192
 
213
193
  module.exports = {
214
- getV2Messages,
215
194
  isValidInputFile,
216
195
  validateAndComputeOutputFilePath,
217
196
  edmx2csn,
@@ -1,12 +1,9 @@
1
1
  "use strict";
2
2
 
3
3
  const messages = {
4
- INVALID_EDMX_METADATA:
5
- "The OData model is not valid. Specify the correct OData model.",
6
- INVALID_OUTPUT_FILE:
7
- "Please provide an output file with either .json or .csn extension.",
8
- MISSING_ENTITIES:
9
- "There are no entities in the OData model. Specify the correct OData model in the provided file.",
4
+ INVALID_EDMX_METADATA: "The OData model is not valid. Specify the correct OData model.",
5
+ INVALID_OUTPUT_FILE: "Please provide an output file with either .json or .csn extension.",
6
+ MISSING_ENTITIES: "There are no entities in the OData model. Specify the correct OData model in the provided file.",
10
7
  SPECIFY_INPUT_FILE: "Specify an input file that exists.",
11
8
  SPECIFY_OUTPUT_FILE: "Specify an output file.",
12
9
  MISSING_ENTITY_CONTAINER: 'There is no entity container in the OData model. Specify the correct OData model.',
@@ -18,4 +15,6 @@ function getMessages() {
18
15
  return messages;
19
16
  }
20
17
 
21
- module.exports = getMessages;
18
+ module.exports = {
19
+ getMessages
20
+ };