@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
package/CHANGELOG.md CHANGED
@@ -6,6 +6,60 @@ This project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
7
  The format is based on [Keep a Changelog](http://keepachangelog.com/).
8
8
 
9
+ ## Version 4.5.0 - 2021-09-23
10
+
11
+ ### Added
12
+
13
+ - `cds import` supports import of functions, action, functionimport and actionimport from both OData v2 and v4 edmx files.
14
+
15
+ ### Changed
16
+
17
+ - `cds init` uses latest `Maven Java archetype` version `1.18.1` for creating Java projects.
18
+
19
+ ### Fixed
20
+
21
+ - `cds watch` additional CLI args were not passed to launched process, e.g. `--odata x4`.
22
+ - `cds import` fix for TypeError issue during OData v4 edmx conversion to csn.
23
+ - `cds import` fix for multi-line documentation text in OData v2 edmx file.
24
+ - `cds import` fix for v4 csn generation when non standard OData vocabularies are referred.
25
+ - `cds add data`
26
+ - no longer fails for entities without keys
27
+ - no longer creates csv files for synthetic draft entities
28
+ - no longer creates entries for virtual fields
29
+ - no longer skips over entities marked with `@cds.persistence.skip`, which is true for 'external' entities created by `cds import`.
30
+ - `cds init --add` handles comma list correctly.
31
+
32
+
33
+ ## Version 4.4.2 - 2021-09-03
34
+
35
+ ### Fixed
36
+
37
+ - Use latest `cds-sidecar-client` to fix file upload in `cds activate`
38
+
39
+ ## Version 3.5.3 - 2021-08-31
40
+
41
+ ### Fixed
42
+
43
+ - Use latest `cds-sidecar-client` to fix file upload in `cds activate`
44
+
45
+ ## Version 4.4.1 - 2021-08-03
46
+
47
+ ### Fixed
48
+
49
+ - Use `@sap/eslint-plugin-cds@2.0.4`
50
+
51
+ ## Version 4.4.0 - 2021-08-02
52
+
53
+ ### Changed
54
+
55
+ - `cds init` uses latest `Maven Java archetype` version `1.17.0` for creating Java projects.
56
+
57
+ ### Fixed
58
+
59
+ - Fixed bug when logging in Business Application Studio during `New Project from Template` wizard.
60
+ - `cds import` fix for <documentation> tag in Odata V2 for EntityType and ComplexType.
61
+ - `cds help` does not crash with `this.load is not a function` in exotic installations
62
+
9
63
  ## Version 4.3.3 - 2021-07-26
10
64
 
11
65
  ### Fixed
@@ -18,7 +72,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
18
72
 
19
73
  - Bumped version of `@sap/cds` to 5.3.2
20
74
 
21
- ## Version 4.3.1 - 2021-08-12
75
+ ## Version 3.5.2 - 2021-07-21
76
+
77
+ ### Changed
78
+
79
+ - Bump to latest versions
80
+ - Use latest `cds-sidecar-client` to fix CVE-2021-33502
81
+
82
+ ## Version 4.3.1 - 2021-07-12
22
83
 
23
84
  ### Changed
24
85
 
package/bin/activate.js CHANGED
@@ -49,8 +49,7 @@ async function activate ([projectFolder], options = {}) {
49
49
 
50
50
  Object.assign(options, { projectFolder })
51
51
 
52
- const client = require('@sap/cds-sidecar-client')
53
- await client.apply(injection, options)
52
+ await require('../lib/client/activate').run(injection, options);
54
53
  }
55
54
 
56
55
 
@@ -71,7 +71,7 @@ async function deploy (_model, options) { // NOSONAR
71
71
 
72
72
  const conf = cds.env.requires.db || {}
73
73
  const [,_kind,_db] = /(\w+)?(?::(.*))?/.exec(url||'')
74
- const kind = _kind || conf.use || conf.kind
74
+ const kind = _kind || conf.dialect || conf.use || conf.kind
75
75
 
76
76
  try {
77
77
  var deployer = require ('./to-'+kind)
@@ -10,9 +10,9 @@ exports.deploy = function deploy_to_sqlite (_model, _db, options) {
10
10
  .then (db => db && db.disconnect()) // REVISIT: we should NOT require to disconnect explicitly
11
11
  }
12
12
 
13
- function _database4 (_db,_dbrl,conf) {
13
+ function _database4 (_db,_dbrl,conf) {
14
14
  if (_db) return _db
15
15
  if (_dbrl === 'sqlite') return 'sqlite.db'
16
16
  if (_dbrl === 'sql') return ':memory:'
17
- return conf.credentials && conf.credentials.database
17
+ if (conf.credentials) return conf.credentials.database || conf.credentials.url
18
18
  }
package/bin/extend.js CHANGED
@@ -49,17 +49,15 @@ module.exports = Object.assign(extend, {
49
49
 
50
50
 
51
51
  async function extend ([url], options = {}) {
52
- const client = require('@sap/cds-sidecar-client')
53
-
54
52
  if (options.force) {
55
- if (! await client.question.askBooleanQuestion('Using option --force will overwrite existing files. Continue (yN)? ', false, false)) {
53
+ if (! await require('../lib/client/helper/question').askBooleanQuestion('Using option --force will overwrite existing files. Continue (yN)? ', false, false)) {
56
54
  console.log('Extend cancelled')
57
55
  return
58
56
  }
59
57
  }
60
58
 
61
59
  Object.assign(options, { url })
62
- client.extend(options)
60
+ await require('../lib/client/extend').run(options);
63
61
  }
64
62
 
65
63
 
package/bin/help.js CHANGED
@@ -32,7 +32,7 @@ module.exports = exports = Object.assign (help, {help:`
32
32
 
33
33
  function help (topic) {
34
34
  if (Array.isArray(topic)) topic = topic[0]
35
- const task = !topic ? help : this.load (this.Shortcuts[topic] || topic)
35
+ const task = !topic ? help : this.load && this.load (this.Shortcuts[topic] || topic)
36
36
  if (task && task.help) console.log (task.help
37
37
  .replace(/\n# ([^\n]*)\n/g, `\n\x1b[1m$1\x1b[0m\n`)
38
38
  .replace(/ \*([^*]+)\*/g, ` \x1b[1m$1\x1b[0m`)
package/bin/login.js CHANGED
@@ -5,9 +5,9 @@ module.exports = Object.assign(login, {
5
5
  shortcuts: ['-s', '-d', '-p', '-c'],
6
6
  flags: ['--plain'],
7
7
  get help() {
8
- const client = require('@sap/cds-sidecar-client')
9
- const cdsExtendAndCdsActivate = `${bold('cds extend')} and ${bold('cds activate')}`
10
- const keyring = client.config.keyringDesignation
8
+ const config = require('../lib/client/settings_manager').SettingsManager.config
9
+ const cdsExtendAndCdsActivate = `${bold('cds extend')} and ${bold('cds activate')}`
10
+ const keyring = config.keyringDesignation
11
11
  return `
12
12
  # SYNOPSIS
13
13
 
@@ -42,13 +42,13 @@ module.exports = Object.assign(login, {
42
42
 
43
43
  Authentication data for all logins is saved by <app-url> and <tenant-subdomain> in the
44
44
  desktop keyring (${keyring} on your platform) or, if requested, in config
45
- file ${client.config.paths.auth}.
45
+ file ${config.paths.auth}.
46
46
 
47
47
  To make use of keyring storage, the 'keytar' Node.js module must be installed.
48
48
  A corresponding hint will be given in case the module is not found.
49
49
 
50
50
  Project settings are saved by project directory in config file
51
- ${client.config.paths.settings}.
51
+ ${config.paths.settings}.
52
52
 
53
53
  Note that local storage of authentication data incurs a certain security risk:
54
54
  a malicious locally-running application might be able to perform any action with
@@ -96,7 +96,7 @@ module.exports = Object.assign(login, {
96
96
  # SEE ALSO
97
97
 
98
98
  *cds logout* to remove any authentication data
99
- `;
99
+ `
100
100
  }})
101
101
 
102
102
 
@@ -108,8 +108,7 @@ async function login ([url], options = {}) {
108
108
  })
109
109
  delete options.plain
110
110
 
111
- const client = require('@sap/cds-sidecar-client')
112
- client.login(options)
111
+ await require('../lib/client/auth_manager').loginAndUpdateSettings(options)
113
112
  }
114
113
 
115
114
 
package/bin/logout.js CHANGED
@@ -56,8 +56,7 @@ module.exports = Object.assign(logout, {
56
56
  async function logout ([projectFolder], options = {}) {
57
57
  Object.assign(options, { projectFolder })
58
58
 
59
- const client = require('@sap/cds-sidecar-client')
60
- client.logout(options)
59
+ await require('../lib/client/auth_manager').logout(options)
61
60
  }
62
61
 
63
62
 
package/bin/repl.js CHANGED
@@ -27,7 +27,7 @@ function repl (_, options={}) {
27
27
  const info = colors ? require('../lib/util/term').info : s => s
28
28
  log(info('Welcome to cds repl v'+ cds.version))
29
29
 
30
- const cdsr = require('repl').start ({...options, writer:_writer, ignoreUndefined:true })
30
+ const cdsr = cds.repl = require('repl').start ({...options, writer:_writer, ignoreUndefined:true })
31
31
 
32
32
  cds.lazify (Object.assign (cdsr.context,{ cds,
33
33
  Foo: lazy => new cds.entity ({name:'Foo'}),
package/bin/watch.js CHANGED
@@ -59,22 +59,26 @@ let ignore = /node_modules|\.cds-services\.json$/
59
59
  async function watch ([cwd], {
60
60
  args = ['serve', 'all', '--with-mocks'],
61
61
  ext = extDefaults,
62
- ...etc
62
+ ...options
63
63
  }={}) {
64
64
 
65
- args.push (`--${Object.keys(etc).find(a => /^in-memory\??$/.test(a)) || 'in-memory?'}`)
66
- const env = Object.assign (process.env, { _args: JSON.stringify(args) })
65
+ args.push (`--${Object.keys(options).find(a => /^in-memory\??$/.test(a)) || 'in-memory?'}`)
66
+ args.push (...process.argv.slice(3).filter(a => a !== cwd))
67
+ const env = Object.assign (process.env, {
68
+ _args: JSON.stringify(args),
69
+ _options: JSON.stringify(options)
70
+ })
67
71
  if (typeof ext !== 'string') ext = extDefaults
68
- if ('port' in etc) env.PORT = etc.port
72
+ if ('port' in options) env.PORT = options.port
69
73
  env.NODE_PATH = path.resolve (__dirname,'../node_modules') // allow global sqlite to be resolved
70
74
 
71
75
  let liveReload //= ...
72
76
  let firstTime = true
73
- if (!/false|no/.test(etc['livereload'])) {
77
+ if (!/false|no/.test(options['livereload'])) {
74
78
  ext += ',css,gif,html,jpg,png,svg'
75
79
  liveReload = new (require ('../lib/watch/livereload-server'))
76
80
  env.CDS_LIVERELOAD_PATH = require.resolve ('connect-livereload')
77
- env.CDS_LIVERELOAD_URL = await liveReload.start (etc['livereload'])
81
+ env.CDS_LIVERELOAD_URL = await liveReload.start (options['livereload'])
78
82
  .catch(err => { console.log(`Error starting live reload: ${err.message}`); liveReload = null })
79
83
  } else {
80
84
  ignore = RegExp(`app${path.sep}.*\\.(js|ts)|`+ignore.source) // exclude more frontend stuff
@@ -99,7 +103,7 @@ async function watch ([cwd], {
99
103
  watch: ['*','.env','.cdsrc.json'],
100
104
  script:__filename, cwd, env, ext, ignore,
101
105
  delay: parseInt(process.env.CDS_WATCH_DELAY) || 200,
102
- options: etc
106
+ options
103
107
  }).on('restart', (files)=>{
104
108
  DEBUG && DEBUG (files)
105
109
  if (liveReload) liveReload.markPending(files)
@@ -115,7 +119,7 @@ async function watch ([cwd], {
115
119
  case 'listening':
116
120
  env.PORT = (msg.address && msg.address.port) || msg.port
117
121
  liveReload && liveReload.reload()
118
- firstTime && etc['open'] && require('../lib/watch/open').openURL (etc['open'], env.PORT)
122
+ firstTime && options['open'] && require('../lib/watch/open').openURL (options['open'], env.PORT)
119
123
  firstTime = false
120
124
  break
121
125
  case 'EADDRINUSE': env.PORT = 0; _addr_in_use(msg); break
@@ -0,0 +1,243 @@
1
+ const path = require('path');
2
+ const axios = require('axios');
3
+ const { fse } = require('@sap/cds-foss');
4
+
5
+ const AuthManager = require('./auth_manager');
6
+ const BaseCommand = require('./base_command');
7
+ const { collectFileContent, collectFiles, toUnixPath } = require('./helper/fs_helper');
8
+ const Logger = require('./helper/logging');
9
+
10
+ const COMMAND_URL_SUFFIX = 'asyncActivate';
11
+ const UPDATE_CUSTOM_FILE_URL_SUFFIX = 'updateCustomTenantContent';
12
+
13
+ const I18N_WHITELIST = ['.properties', '.csv'];
14
+
15
+ const JOB_STATUS = {
16
+ QUEUED: 'QUEUED',
17
+ RUNNING: 'RUNNING',
18
+ FINISHED: 'FINISHED',
19
+ FAILED: 'FAILED'
20
+ };
21
+
22
+ async function checkFinished(jobId, statusUrl, params, finisher) {
23
+ const requestOptions = Object.assign({
24
+ method: 'GET'
25
+ }, params.get('reqAuth'));
26
+
27
+ const { status, error } = (await axios(statusUrl, requestOptions)).data;
28
+
29
+ if ((status === JOB_STATUS.FINISHED || status === JOB_STATUS.FAILED) && !finisher.finished) {
30
+ finisher.finished = true;
31
+ clearInterval(finisher.timer);
32
+ if (status === JOB_STATUS.FAILED) {
33
+ return finisher.reject(new Error(`Job ${jobId} failed: ${error.message || JSON.stringify(error)}`));
34
+ }
35
+ Logger.log('Extension active');
36
+ finisher.resolve();
37
+ }
38
+ }
39
+
40
+ module.exports = class Activate extends BaseCommand {
41
+
42
+ static get COMMAND_URL_SUFFIX() { return COMMAND_URL_SUFFIX; }
43
+ static get UPDATE_CUSTOM_FILE_URL_SUFFIX() { return UPDATE_CUSTOM_FILE_URL_SUFFIX; }
44
+
45
+ static async run(injection, paramValues) {
46
+ try {
47
+ const params = await AuthManager.loginAndUpdateSettings(paramValues);
48
+
49
+ if (process.env.DEBUG) {
50
+ Logger.log('Activating extension with parameters ' + JSON.stringify({
51
+ appUrl: params.get('appUrl'),
52
+ projectFolder: params.get('projectFolder'),
53
+ undeploy: params.get('undeploy')
54
+ }));
55
+ } else {
56
+ Logger.log(`Activating extension located at ${params.get('projectFolder')} to URL ${params.get('appUrl')}`);
57
+ }
58
+
59
+ if (!await Activate.checkProject(params.get('projectFolder'))) {
60
+ throw new Error(params.get('projectFolder') + ' is not a valid extension project');
61
+ }
62
+
63
+ try {
64
+ await Activate.runActivate(injection.cds, params);
65
+ if (params.get('wsdl')) {
66
+ await Activate.runCustomFileUpload(params);
67
+ }
68
+ } catch (error) {
69
+ Activate.handleHttpError(error, params);
70
+ }
71
+ } catch (error) {
72
+ Logger.error(undefined, error);
73
+ }
74
+ }
75
+
76
+ static async collectExtensionFiles(cds, projectFolder) {
77
+
78
+ const absoluteFolder = projectFolder;
79
+ const dbFolder = path.join(absoluteFolder, 'db');
80
+ const srvFolder = path.join(absoluteFolder, 'srv');
81
+
82
+ const resolvedPathDb = cds.resolve(dbFolder, { root: dbFolder });
83
+ const resolvedPathSrv = cds.resolve(srvFolder, { root: srvFolder });
84
+
85
+ let resolvedPaths = [];
86
+ if (resolvedPathSrv) {
87
+ resolvedPaths = resolvedPaths.concat(resolvedPathSrv);
88
+ }
89
+
90
+ if (resolvedPathDb) {
91
+ resolvedPaths = resolvedPaths.concat(resolvedPathDb);
92
+ }
93
+
94
+ let allFiles = new Map();
95
+
96
+ if (resolvedPaths.length < 1) {
97
+ Logger.log('No extension files found');
98
+ return allFiles;
99
+ }
100
+
101
+ const csn = await cds.load(resolvedPaths);
102
+ const sources = csn.$sources || csn._sources;
103
+
104
+ const filteredSources = sources && sources.filter((entry) => {
105
+ if (! entry.startsWith(projectFolder)) {
106
+ /* Current file might be below the base model's node_modules, in which case compilation on server should succeed.
107
+ Otherwise, compilation will probably fail, with this message giving a hint. */
108
+ Logger.debug(`Source file outside project folder will not be uploaded: ${entry}`);
109
+ return false;
110
+ }
111
+ const unixFilename = toUnixPath(entry);
112
+ return !/.*node_modules\/_base\/.*/.test(unixFilename);
113
+ }) || [];
114
+
115
+ const sourceFiles = await collectFileContent(filteredSources, projectFolder);
116
+ allFiles = new Map([...allFiles, ...sourceFiles]);
117
+
118
+ const modelFiles4I18n = { $sources: filteredSources };
119
+
120
+ let i18nFolders = cds.localize.folders4(modelFiles4I18n);
121
+ i18nFolders = i18nFolders.filter(i18nFolder => {
122
+ const unixFilename = toUnixPath(i18nFolder);
123
+ return !(/.*node_modules\/_base\/.*/.test(unixFilename));
124
+ });
125
+
126
+ i18nFolders.forEach(async(folder) => {
127
+ const i18nFiles = collectFiles(projectFolder, folder, entry => {
128
+ const ext = path.extname(entry);
129
+ return I18N_WHITELIST.includes(ext);
130
+ });
131
+ allFiles = new Map([...allFiles, ...i18nFiles]);
132
+ });
133
+
134
+ try {
135
+ const pkgJson = await fse.readFile(path.join(absoluteFolder, 'package.json'), 'utf-8');
136
+ allFiles.set('package.json', pkgJson);
137
+ } catch(e) {
138
+ Logger.log('No package.json file found');
139
+ Logger.error('No package.json file found', e);
140
+ }
141
+
142
+ return allFiles;
143
+ }
144
+
145
+ static collectCustomFiles(projectFolder) {
146
+ const wsdlFolder = path.join(projectFolder, 'wsdl');
147
+
148
+ try {
149
+ const wsdlFiles = collectFiles(projectFolder, wsdlFolder);
150
+ if (wsdlFiles.size === 0) {
151
+ Logger.log('No custom files found');
152
+ }
153
+ return wsdlFiles;
154
+ } catch (error) {
155
+ Logger.error(undefined, error);
156
+ throw new Error('No custom files found');
157
+ }
158
+ }
159
+
160
+ static async runActivate(cds, params) {
161
+ let activationUrl;
162
+
163
+ return Activate.collectExtensionFiles(cds, params.get('projectFolder'))
164
+ .then(allFiles => {
165
+ if (allFiles.size === 0) {
166
+ return null;
167
+ }
168
+
169
+ Logger.log('Activating extension...');
170
+
171
+ activationUrl = Activate.getMtxApiUrl(params.get('appUrl'), COMMAND_URL_SUFFIX);
172
+ const requestOptions = Object.assign({
173
+ method: 'POST',
174
+ headers: {},
175
+ data: {
176
+ extension: [...allFiles],
177
+ undeployExtension: params.get('undeploy')
178
+ }
179
+ }, params.get('reqAuth'));
180
+ requestOptions.headers['Content-Type'] = 'application/json';
181
+
182
+ return axios(activationUrl, requestOptions);
183
+ })
184
+ .then(response => {
185
+ if (response === null) {
186
+ return;
187
+ }
188
+
189
+ const { jobID: jobId, statusPath } = response.data;
190
+
191
+ if (jobId === undefined) {
192
+ throw new Error('Activation failed: did not receive job ID');
193
+ }
194
+
195
+ if (!statusPath) {
196
+ throw new Error(`Job ${jobId} failed: did not receive job-status path`);
197
+ }
198
+
199
+ Logger.debug(`Job ID: ${jobId}`);
200
+
201
+ return new Promise((resolve, reject) => {
202
+ const statusUrl = Activate.concatUrls(params.get('appUrl'), statusPath);
203
+ const finisher = {
204
+ resolve,
205
+ reject,
206
+ finished: false,
207
+ timer: null
208
+ };
209
+ finisher.timer = setInterval(checkFinished.bind(undefined, jobId, statusUrl, params, finisher), 250);
210
+ });
211
+ });
212
+ }
213
+
214
+ static async runCustomFileUpload(params) {
215
+ Logger.log(`Uploading custom files to ${params.get('appUrl')}...`);
216
+
217
+ const customFiles = Activate.collectCustomFiles(params.get('projectFolder'));
218
+
219
+ const customFilesSerialized = [];
220
+ customFiles.forEach((value, key) => {
221
+ customFilesSerialized.push({
222
+ category: "wsdl",
223
+ name: key,
224
+ content: value
225
+ });
226
+ });
227
+
228
+ const customFileUrl = Activate.getMtxApiUrl(params.get('appUrl'), Activate.UPDATE_CUSTOM_FILE_URL_SUFFIX);
229
+ const requestOptions = Object.assign({
230
+ method: 'POST',
231
+ headers: {},
232
+ data: {
233
+ content: customFilesSerialized
234
+ }
235
+ }, params.get('reqAuth'));
236
+ requestOptions.headers['Content-Type'] = 'application/json';
237
+
238
+ await axios(customFileUrl, requestOptions);
239
+
240
+ Logger.log('Custom file upload finished');
241
+ }
242
+
243
+ }
@@ -0,0 +1,115 @@
1
+ const util = require('util');
2
+ const axios = require('axios');
3
+
4
+ const Logger = require('./helper/logging');
5
+ const { SettingsManager } = require('./settings_manager');
6
+ const { ParamCollection } = require('./params');
7
+
8
+ function addTokenInfo(params, authData) {
9
+ params.set('token', authData.access_token);
10
+ if (authData.expires_in) {
11
+ params.set('tokenExpirationDate', Date.now() + authData.expires_in * 1000);
12
+ } else {
13
+ params.delete('tokenExpirationDate');
14
+ }
15
+ if (authData.refresh_token) {
16
+ params.set('refreshToken', authData.refresh_token);
17
+ } else {
18
+ params.delete('refreshToken');
19
+ }
20
+ }
21
+ function addUrlInfo(params, authData) {
22
+ params.set('passcodeUrl', authData.passcode_url);
23
+ }
24
+ function getTokenUrl(params) {
25
+ const tokenUrl = new URL(params.get('tokenUrl'));
26
+ tokenUrl.search = `subdomain=${params.get('subdomain')}`;
27
+
28
+ if (params.has('refreshToken')) {
29
+ tokenUrl.search += `&refresh_token=${params.get('refreshToken')}`;
30
+ } else if (params.has('passcode')) {
31
+ tokenUrl.search += `&passcode=${params.get('passcode')}`;
32
+ }
33
+ if (params.has('clientid')) {
34
+ tokenUrl.search += `&clientid=${params.get('clientid')}&clientsecret=${params.get('clientsecret')}`;
35
+ }
36
+ return tokenUrl.toString();
37
+ }
38
+
39
+ async function retrieveTokenOrPasscodeUrl(params) {
40
+ if (params.has('token')) {
41
+ if (params.get('renewLogin') || params.get('tokenExpirationDate') <= Date.now()) {
42
+ Logger.debug((params.get('renewLogin') ? 'Renewing' : 'Refreshing expired') + ' authentication token');
43
+ params.delete('token');
44
+ params.delete('tokenExpirationDate');
45
+ } else {
46
+ return;
47
+ }
48
+ }
49
+ if (!params.has('passcode') && !params.has('refreshToken') && params.has('passcodeUrl')) {
50
+ return;
51
+ }
52
+
53
+ let retry;
54
+ let authData;
55
+ do {
56
+ const tokenUrl = getTokenUrl(params);
57
+ Logger.debug(`Getting authentication token or passcode URL from ${(params.obfuscateQueryParams(tokenUrl))}`);
58
+
59
+ let response;
60
+ try {
61
+ response = await axios(tokenUrl.toString(), { method: 'GET' });
62
+ } catch (error) {
63
+ throw new Error(`Failed to get authentication token: ${error}.\nResponse body: ${util.inspect(response && response.data)}`);
64
+ }
65
+ authData = response.data;
66
+
67
+ retry = !authData.access_token && params.has('refreshToken') && (params.delete('refreshToken'), true);
68
+ } while (retry);
69
+
70
+ if (authData.access_token) {
71
+ addTokenInfo(params, authData);
72
+ } else {
73
+ addUrlInfo(params, authData);
74
+ }
75
+ }
76
+
77
+
78
+ module.exports = class AuthManager {
79
+
80
+ static async loginAndUpdateSettings(paramValues) {
81
+ SettingsManager.init();
82
+ const params = new ParamCollection(paramValues);
83
+ await SettingsManager.loadAndMergeSettings(params);
84
+ if (params.has('username')) {
85
+ params.set('reqAuth', { auth: { username: params.get('username'), password: params.get('password') } });
86
+ } else {
87
+ await retrieveTokenOrPasscodeUrl(params);
88
+ if (!params.has('token')) {
89
+ await SettingsManager.saveSettings(params);
90
+ throw new Error('Failed to login: no valid token or passcode provided. ' +
91
+ 'Get a passcode' + (params.get('passcodeUrl') ? ' from ' + params.get('passcodeUrl') : '') +
92
+ ' and supply the code when re-running the command');
93
+ }
94
+ params.set('reqAuth', { headers: { Authorization: 'Bearer ' + params.get('token') } });
95
+ }
96
+
97
+ await SettingsManager.saveSettings(params); // saves token conditionally
98
+
99
+ return params;
100
+ }
101
+
102
+ static async logout(paramValues) {
103
+ SettingsManager.init();
104
+ const params = new ParamCollection(paramValues);
105
+ if (params.get('clearInvalid')) {
106
+ await SettingsManager.deleteInvalid();
107
+ } else {
108
+ await SettingsManager.deleteToken(params);
109
+ if (params.get('deleteSettings')) {
110
+ await SettingsManager.deleteSettingsWithoutToken(params);
111
+ }
112
+ }
113
+ }
114
+
115
+ }
@@ -0,0 +1,55 @@
1
+ const path = require('path');
2
+ const { fse } = require('@sap/cds-foss');
3
+
4
+ const Logger = require('./helper/logging');
5
+
6
+ const MTX_URL_PATH_PREFIX = 'mtx/v1/model/';
7
+ const CUSTOM_FILE_URL_SUFFIX = 'customTenantContent';
8
+
9
+ module.exports = class BaseCommand {
10
+
11
+ static get CUSTOM_FILE_URL_SUFFIX() { return CUSTOM_FILE_URL_SUFFIX; }
12
+
13
+ static async checkProject(folder) {
14
+ return fse.pathExists(path.join(folder, 'package.json'));
15
+ }
16
+
17
+ static async handleHttpError(error, params) {
18
+ if (!('isAxiosError' in error)) {
19
+ throw error;
20
+ }
21
+
22
+ switch (error.response.status) {
23
+ case 401:
24
+ Logger.error('Request failed: authorization might have expired. Please retry with valid passcode' +
25
+ (params.has('passcodeUrl') ? ` from ${params.get('passcodeUrl')}` : ''), error);
26
+ break;
27
+ case 404:
28
+ Logger.error(`Request failed. Please check application URL ${params.get('appUrl')}`, error);
29
+ break;
30
+ default:
31
+ // Including 422 (compilation or native-extension error).
32
+ Logger.error(undefined, error);
33
+ }
34
+ }
35
+
36
+ static concatUrls(base, ...paths) {
37
+ function trimBeginSlash(str) {
38
+ return str.replace(/^\//, '');
39
+ }
40
+ function trimEndSlash(str) {
41
+ return str.replace(/\/$/, '');
42
+ }
43
+ function trimSlashes(str) {
44
+ return trimBeginSlash(trimEndSlash(str));
45
+ }
46
+ const concatenated = new URL(base);
47
+ concatenated.pathname = trimEndSlash(concatenated.pathname) + '/' +
48
+ paths.map((p, i) => i < paths.length - 1 ? trimSlashes(p) : trimBeginSlash(p)).join('/');
49
+ return concatenated.toString();
50
+ }
51
+
52
+ static getMtxApiUrl(url, action) {
53
+ return BaseCommand.concatUrls(url, MTX_URL_PATH_PREFIX, action);
54
+ }
55
+ }