@sap/cds-dk 4.4.2 → 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 (39) hide show
  1. package/CHANGELOG.md +24 -0
  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/login.js +7 -8
  7. package/bin/logout.js +1 -2
  8. package/bin/repl.js +1 -1
  9. package/bin/watch.js +12 -8
  10. package/lib/client/activate.js +243 -0
  11. package/lib/client/auth_manager.js +115 -0
  12. package/lib/client/base_command.js +55 -0
  13. package/lib/client/extend.js +179 -0
  14. package/lib/client/helper/cf_helper.js +186 -0
  15. package/lib/client/helper/dependency_helper.js +26 -0
  16. package/lib/client/helper/env_helper.js +4 -0
  17. package/lib/client/helper/fs_helper.js +83 -0
  18. package/lib/client/helper/logging.js +37 -0
  19. package/lib/client/helper/question.js +50 -0
  20. package/lib/client/helper/string_helper.js +53 -0
  21. package/lib/client/helper/url_helper.js +17 -0
  22. package/lib/client/index.js +14 -0
  23. package/lib/client/params.js +435 -0
  24. package/lib/client/settings_manager.js +526 -0
  25. package/lib/import/index.js +5 -18
  26. package/lib/import/v2parser.js +282 -27
  27. package/lib/import/v4parser.js +341 -54
  28. package/lib/init/constants.js +1 -1
  29. package/lib/init/index.js +13 -10
  30. package/lib/init/template/data/index.js +38 -46
  31. package/lib/init/template/hana/index.js +2 -2
  32. package/lib/init/template/lint/index.js +8 -6
  33. package/lib/init/template/mta/projectReader.js +8 -5
  34. package/lib/init/template/nodejs/files/.vscode/extensions.json +1 -1
  35. package/lib/init/template/nodejs/index.js +1 -8
  36. package/lib/lint/Linter.js +62 -60
  37. package/lib/lint/io.js +9 -33
  38. package/npm-shrinkwrap.json +7684 -12883
  39. package/package.json +4 -5
@@ -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
+ };
@@ -35,6 +35,7 @@ async function isValidInputFile(inputFileLoc) {
35
35
  }
36
36
  else if (inputFileLoc.endsWith(".edmx") || inputFileLoc.endsWith(".xml")) {
37
37
  file = fileResolved;
38
+ defaultFileName = file;
38
39
  } else {
39
40
  throw new Error(messages.INVALID_INPUT_FILE);
40
41
  }
@@ -65,12 +66,9 @@ function validateAndComputeOutputFilePath(outputFileLoc) {
65
66
  !outputFileLoc.endsWith("/")) {
66
67
  // Output file extension is not mentioned ("./srv/external/test", "test", "c:\windows\output\test")
67
68
  filePath = outputFileLoc + ".csn";
68
- } else { // File name not specified ("./srv/external/", "c:\windows\output\test\")
69
- if (outputFileLoc.endsWith("/") || outputFileLoc.endsWith("\\")) {
70
- filePath = outputFileLoc + _getDefaultFileName();
71
- } else {
72
- filePath = outputFileLoc + "/" + _getDefaultFileName();
73
- }
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";
74
72
  }
75
73
 
76
74
  return filePath;
@@ -137,7 +135,7 @@ async function edmx2csn(edmx, filename, context) {
137
135
  }
138
136
  let csn = await generateCSN(edmx, false, true, context);
139
137
  if (filename) {
140
- // Replace namespace by given filename in the csn file instead of edmx file
138
+ // Replace namespace by given filename in the csn file instead of edmx file
141
139
  const re = RegExp(`([("/])${namespace}([/").])`, 'g');
142
140
  const fn = `$1${path.parse(filename).name}$2`;
143
141
  csn = csn.replace(re, fn);
@@ -188,17 +186,6 @@ async function generateCSN(edmx, ignorePersistenceSkip, mockServerUc, context) {
188
186
  });
189
187
  }
190
188
 
191
- function _getDefaultFileName() {
192
- let nameSpace;
193
- if (defaultFileName === undefined) {
194
- nameSpace = edmxv2CSN.getNamespace();
195
- if (nameSpace) {
196
- defaultFileName = nameSpace + ".csn"; //".json";
197
- }
198
- }
199
- return defaultFileName;
200
- }
201
-
202
189
  async function saveCSNModel(csnDataModel, fileLoc) {
203
190
  await fs.write(fileLoc, csnDataModel, "utf-8");
204
191
  }