@sap/cds-dk 4.4.2 → 4.5.4

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 (45) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/bin/activate.js +1 -2
  3. package/bin/deploy/index.js +3 -2
  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/import.js +105 -23
  8. package/bin/login.js +7 -8
  9. package/bin/logout.js +1 -2
  10. package/bin/repl.js +1 -1
  11. package/bin/watch.js +30 -11
  12. package/lib/client/activate.js +243 -0
  13. package/lib/client/auth_manager.js +115 -0
  14. package/lib/client/base_command.js +55 -0
  15. package/lib/client/extend.js +179 -0
  16. package/lib/client/helper/cf_helper.js +186 -0
  17. package/lib/client/helper/dependency_helper.js +26 -0
  18. package/lib/client/helper/env_helper.js +4 -0
  19. package/lib/client/helper/fs_helper.js +83 -0
  20. package/lib/client/helper/logging.js +37 -0
  21. package/lib/client/helper/question.js +50 -0
  22. package/lib/client/helper/string_helper.js +53 -0
  23. package/lib/client/helper/url_helper.js +17 -0
  24. package/lib/client/index.js +14 -0
  25. package/lib/client/params.js +435 -0
  26. package/lib/client/settings_manager.js +526 -0
  27. package/lib/import/common.js +24 -1
  28. package/lib/import/importapi.js +1 -1
  29. package/lib/import/index.js +25 -41
  30. package/lib/import/message.js +10 -6
  31. package/lib/import/v2parser.js +301 -47
  32. package/lib/import/v4parser.js +391 -71
  33. package/lib/init/constants.js +1 -1
  34. package/lib/init/index.js +15 -10
  35. package/lib/init/template/data/index.js +38 -46
  36. package/lib/init/template/hana/index.js +2 -2
  37. package/lib/init/template/lint/index.js +8 -6
  38. package/lib/init/template/mta/projectReader.js +8 -5
  39. package/lib/init/template/nodejs/files/.vscode/extensions.json +1 -1
  40. package/lib/init/template/nodejs/index.js +1 -8
  41. package/lib/lint/Linter.js +62 -60
  42. package/lib/lint/generators.js +1 -1
  43. package/lib/lint/io.js +9 -33
  44. package/npm-shrinkwrap.json +5179 -10848
  45. package/package.json +5 -5
@@ -0,0 +1,186 @@
1
+ const os = require('os');
2
+ const { execFile } = require('child_process');
3
+
4
+ const Logger = require('./logging');
5
+ const Question = require('./question');
6
+ const { formatObjectsFixedWidth } = require('./string_helper');
7
+ const { schemaRegex } = require('./url_helper');
8
+
9
+ function runCf(args, callback) {
10
+ const env = Object.assign({}, process.env, { LC_ALL: 'en_US.UTF-8' }); // enforce English headings
11
+ return execFile('cf', args, { env }, callback);
12
+ }
13
+
14
+ function getErrorMessage(error, stdout) {
15
+ return stdout.includes('Not logged in')
16
+ ? 'not logged in'
17
+ : error && error.message || stdout.replace('\n', ' ');
18
+ }
19
+
20
+ async function getAppFromSuggestions() {
21
+ const apps = await getApps();
22
+ if (apps.length === 0) {
23
+ Logger.debug('Failed to obtain list of apps from CF');
24
+ return undefined;
25
+ }
26
+
27
+ let choice;
28
+ let choiceNr;
29
+ do {
30
+ choice = await Question.askQuestion(`App URL not given. From the following apps running in the CF org and space you are logged into,
31
+ select one by entering a number or leave empty to quit:
32
+
33
+ ${formatObjectsFixedWidth(apps, ['name', 'url'], 3, { numbering: true })}
34
+
35
+ Your choice: `);
36
+
37
+ if (!choice) {
38
+ Logger.debug('No app URL selected');
39
+ return undefined;
40
+ }
41
+ choiceNr = Number(choice);
42
+ } while (choiceNr > apps.length + 1);
43
+
44
+ return apps[choiceNr - 1];
45
+ }
46
+
47
+ async function getApps(nameRegex = /(?<!-db)$/) {
48
+ Logger.debug('Getting running apps from CF');
49
+
50
+ return new Promise(resolve => {
51
+ runCf(['apps'], function(error, stdout) {
52
+ if (! /^name\b/m.test(stdout)) {
53
+ return resolve(errorResult(getErrorMessage(error, stdout)));
54
+ }
55
+
56
+ const appLines = getAppLines(stdout);
57
+
58
+ const heading = appLines.shift();
59
+ if (!heading || appLines.length === 0) {
60
+ return resolve(errorResult('CF reports no running apps'));
61
+ }
62
+
63
+ const titles = heading.includes('processes') ? ['processes', 'routes'] : ['instances', 'urls'];
64
+ const offsets = {
65
+ name: 0,
66
+ instances: heading.indexOf(titles[0]),
67
+ urls: heading.indexOf(titles[1])
68
+ };
69
+
70
+ const result = [];
71
+
72
+ appLines.forEach((line) => {
73
+ if (Number(getSubstring(line, offsets.instances).replace(/(^.+:|\/.*)/g, '')) === 0) {
74
+ return;
75
+ }
76
+ const name = getSubstring(line, offsets.name);
77
+ if (!nameRegex.test(name)) {
78
+ return;
79
+ }
80
+ const urls = getSubstring(line, offsets.urls, true);
81
+ if (!urls) {
82
+ return;
83
+ }
84
+ urls.split(/,\s*/).forEach((url) => {
85
+ if (!url.includes('://')) {
86
+ url = 'https://' + url;
87
+ }
88
+ result.push({
89
+ name,
90
+ url
91
+ });
92
+ });
93
+ });
94
+
95
+ return resolve(result);
96
+ });
97
+ });
98
+
99
+ function getSubstring(line, offset, allowSpace = false) {
100
+ if (allowSpace) {
101
+ return line.substr(offset).trimRight();
102
+ }
103
+ const match = /^\S+/.exec(line.substr(offset));
104
+ if (!match) {
105
+ return undefined;
106
+ }
107
+ return match[0];
108
+ }
109
+
110
+ function getAppLines(stdout) {
111
+ let relevant = false;
112
+ return stdout.split('\n').filter((line) => {
113
+ if (/^name\b/.test(line)) {
114
+ relevant = true;
115
+ return true;
116
+ }
117
+ return line.length && relevant;
118
+ });
119
+ }
120
+
121
+ function errorResult(message) {
122
+ Logger.debug(`Failed to get apps: ${message}`);
123
+ return [];
124
+ }
125
+ }
126
+
127
+ async function getSubdomain(appName) {
128
+ Logger.debug('Getting subdomain from CF env');
129
+
130
+ return new Promise(resolve => {
131
+ runCf(['env', appName], function(error, stdout) {
132
+ if (! /Provided:$/m.test(stdout)) {
133
+ if (stdout.includes('not found')) {
134
+ return resolve(errorResult(`CF reports that app '${appName}' is not found`));
135
+ }
136
+ return resolve(errorResult(getErrorMessage(error, stdout)));
137
+ }
138
+
139
+ const nl = os.EOL;
140
+ const match = new RegExp(`^System-Provided:${nl}(?:\\{${nl}((.|${nl})*?)^}|((.|${nl})*?^}))`, 'm').exec(stdout);
141
+ if (!match) {
142
+ return resolve(errorResult(`CF reports no system-provided environment for app '${appName}'`));
143
+ }
144
+
145
+ let content = (match[1] || match[3]).replace(/^(\s*)(?!")(\w+)/m, '"$2"');
146
+ if (! /^\s*{/.test(content)) {
147
+ content = '{' + content + '}';
148
+ }
149
+ let value = JSON.parse(content);
150
+ for (const segment of ['VCAP_SERVICES', 'xsuaa', 0, 'credentials', 'identityzone']) {
151
+ value = value[segment];
152
+ if (!value) {
153
+ return resolve(errorResult(`CF reports insufficient information for app '${appName}' at segment '${segment}'`));
154
+ }
155
+ }
156
+
157
+ return resolve(value);
158
+ });
159
+ });
160
+
161
+ function errorResult(message) {
162
+ Logger.debug(message);
163
+ return undefined;
164
+ }
165
+ }
166
+
167
+ async function getAppName(appUrl) {
168
+ function equalIgnoringTls(url1, url2) {
169
+ return url1.replace(schemaRegex, '') === url2.replace(schemaRegex, '');
170
+ }
171
+
172
+ Logger.debug('Getting app name from CF apps');
173
+ const apps = await getApps();
174
+ const app = apps.find(app => equalIgnoringTls(app.url, appUrl));
175
+ if (!app) {
176
+ Logger.debug(`Failed to find app with URL ${appUrl}. Running apps: ${apps.map(app => app.name)}`);
177
+ }
178
+ return app && app.name;
179
+ }
180
+
181
+ module.exports = {
182
+ getAppFromSuggestions,
183
+ getAppName,
184
+ getSubdomain,
185
+ getApps
186
+ };
@@ -0,0 +1,26 @@
1
+ const os = require('os');
2
+ const { execFileSync } = require('child_process');
3
+
4
+ const Logger = require('./logging');
5
+
6
+ let cachedDir;
7
+
8
+ function globalModulesDir() {
9
+ if (!cachedDir) {
10
+ cachedDir = execFileSync('npm', ['root', '-g'], {
11
+ encoding: 'utf8',
12
+ shell: os.platform() === 'win32'
13
+ }).trim();
14
+ }
15
+ return cachedDir;
16
+ }
17
+
18
+ module.exports = function requireGlobal(packageId) {
19
+ try {
20
+ const packagePath = require.resolve(packageId, { paths: [globalModulesDir()] });
21
+ return require(packagePath);
22
+ } catch (error) {
23
+ // Ignore error.
24
+ Logger.debug(`Failed to find module '${packageId}' in ${globalModulesDir()}: ${error}`);
25
+ }
26
+ }
@@ -0,0 +1,4 @@
1
+ module.exports = function isBas() {
2
+ const env = Object.entries(process.env);
3
+ return env.some(([key, value]) => key === 'WS_BASE_URL' && value.includes('applicationstudio') || /THEIA_(DEFAULT_)?PLUGINS/.test(key));
4
+ }
@@ -0,0 +1,83 @@
1
+ const path = require('path');
2
+ const { fse } = require('@sap/cds-foss');
3
+
4
+ async function collectFileContent(filePaths, root) {
5
+ const resultMap = new Map();
6
+ filePaths.forEach((filePath) => {
7
+ const fileContent = fse.readFileSync(filePath);
8
+ resultMap.set(toUnixPath(path.relative(root, filePath)), fileContent.toString('utf-8'));
9
+ });
10
+ return resultMap;
11
+ }
12
+
13
+ /**
14
+ * Returns map of files with content for a given directory
15
+ * @param root Root directory
16
+ * @param sourceDir Directory to start the traversal
17
+ * @param filter Filter function
18
+ * @returns {Promise.<Map>}
19
+ */
20
+ function collectFiles(root, sourceDir, filter) {
21
+ const files = new Map();
22
+ _traverseFileSystem(root, sourceDir, files, filter);
23
+
24
+ return files;
25
+ }
26
+
27
+ function _traverseFileSystem(root, sourceDir, files, filter) {
28
+ let absoluteDir = path.resolve(root, sourceDir);
29
+ if (!fse.existsSync(absoluteDir)) {
30
+ return;
31
+ }
32
+ absoluteDir = fse.realpathSync(absoluteDir);
33
+ if (fse.statSync(absoluteDir)) {
34
+ fse.readdirSync(absoluteDir)
35
+ .map(subDirEntry => path.join(absoluteDir, subDirEntry))
36
+ .forEach(entry => {
37
+ const stats = fse.statSync(entry)
38
+ if (stats.isDirectory()) {
39
+ _traverseFileSystem(root, entry, files, filter);
40
+ }
41
+ if (stats.isFile() && (!filter || filter.call(this, entry))) {
42
+ const fileContent = fse.readFileSync(entry);
43
+ files.set(toUnixPath(path.relative(root, entry)), fileContent.toString('utf-8'));
44
+ }
45
+ });
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Converts Windows paths by using unix separators
51
+ * @param aPath a path
52
+ * @returns {string}
53
+ */
54
+ function toUnixPath(aPath) {
55
+ return aPath.replace(/\\/g, '/');
56
+ }
57
+
58
+ /**
59
+ * Normalizes filename to os specific format (but does not check for existence)
60
+ * @param {string} filename
61
+ */
62
+ function normalizePath(filename) {
63
+ return path.normalize(filename);
64
+ }
65
+
66
+ /**
67
+ * Returns whether a given path is a parent of another path.
68
+ * @param maybeParent a potential parent path
69
+ * @param maybeChild a potential child path
70
+ * @returns {boolean} whether `maybeParent` is a parent path of `maybeChild`
71
+ */
72
+ function isParent(maybeParent, maybeChild) {
73
+ return ! path.relative(maybeParent, maybeChild).startsWith('..');
74
+ }
75
+
76
+
77
+ module.exports = {
78
+ collectFileContent,
79
+ collectFiles,
80
+ toUnixPath,
81
+ normalizePath,
82
+ isParent
83
+ };
@@ -0,0 +1,37 @@
1
+ /* eslint-disable no-console */
2
+
3
+ module.exports = class Logger {
4
+
5
+ static log(logText) {
6
+ console.log(logText);
7
+ }
8
+
9
+ static error(errorText, error) {
10
+ console.error(this.getMessage(errorText, error));
11
+ }
12
+
13
+ static debug(debugText, error) {
14
+ if (process.env.DEBUG) {
15
+ console.error(this.getMessage(debugText, error));
16
+ }
17
+ }
18
+
19
+ static getMessage(errorText, error) {
20
+ let message = errorText && errorText + '\n' || '';
21
+ if (!error) {
22
+ return message;
23
+ }
24
+ if (process.env.DEBUG) {
25
+ if ('response' in error && error.response.data) {
26
+ const body = error.response.data;
27
+ if (body.error && body.error.message) {
28
+ message += body.error.message + '\n';
29
+ }
30
+ }
31
+ message += '\n' + error.stack;
32
+ } else {
33
+ message += error.message;
34
+ }
35
+ return message;
36
+ }
37
+ }
@@ -0,0 +1,50 @@
1
+ const readline = require('readline');
2
+
3
+ module.exports = class Question {
4
+
5
+ static askQuestion(question, defaultValue, hidden = false) {
6
+ const rl = readline.createInterface({
7
+ input: process.stdin,
8
+ output: process.stdout
9
+ });
10
+
11
+ if (defaultValue) {
12
+ question += '(' + defaultValue + ') ';
13
+ }
14
+ return new Promise(resolve => {
15
+ rl.question(question, answer => {
16
+ answer = answer.replace(/\r?\n|\r/g, '');
17
+ if (answer.length === 0 && defaultValue) {
18
+ answer = defaultValue;
19
+ }
20
+ resolve(answer);
21
+ rl.close();
22
+ });
23
+
24
+ if (hidden) {
25
+ const rla = rl;
26
+ rla._writeToOutput = function _writeToOutput() {
27
+ rla.output.write('*');
28
+ };
29
+ }
30
+ });
31
+ }
32
+
33
+ static askBooleanQuestion(question) {
34
+ const rl = readline.createInterface({
35
+ input: process.stdin,
36
+ output: process.stdout
37
+ });
38
+
39
+ return new Promise(resolve => {
40
+ rl.question(question, answer => {
41
+ if (answer === 'y' || answer === 'Y') {
42
+ resolve(true);
43
+ } else {
44
+ resolve(false);
45
+ }
46
+ rl.close();
47
+ });
48
+ });
49
+ }
50
+ }
@@ -0,0 +1,53 @@
1
+ const os = require('os');
2
+
3
+ function intWidth(number) {
4
+ return Math.floor(Math.log10(number)) + 1;
5
+ }
6
+
7
+ function formatObjectsFixedWidth(objects, columns, distance, options = {}) {
8
+ const { numbering = false } = options;
9
+
10
+ const maxWidths = columns.reduce((result, column) => Object.assign(result, {
11
+ [column]: Math.max(...objects.map((object) => ('' + object[column]).length))
12
+ }), {});
13
+ const numberingWidth = intWidth(objects.length);
14
+ const separator = ' '.repeat(distance);
15
+ let result = '';
16
+
17
+ for (let o = 0; o < objects.length; o++) {
18
+ const object = objects[o];
19
+ if (numbering) {
20
+ result += ' '.repeat(numberingWidth - intWidth(o + 1));
21
+ result += o + 1;
22
+ result += '.';
23
+ result += separator;
24
+ }
25
+ for (let c = 0; c < columns.length; c++) {
26
+ const column = columns[c];
27
+ const value = '' + object[column];
28
+ result += value;
29
+ result += ' '.repeat(maxWidths[column] - value.length);
30
+ if (c < columns.length - 1) {
31
+ result += separator;
32
+ } else {
33
+ result += o < objects.length - 1
34
+ ? os.EOL
35
+ : '';
36
+ }
37
+ }
38
+ }
39
+
40
+ return result;
41
+ }
42
+
43
+ /**
44
+ * from https://developer.mozilla.org/de/docs/Web/JavaScript/Guide/Regular_Expressions
45
+ */
46
+ function escapeRegex(str) {
47
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
48
+ }
49
+
50
+ module.exports = {
51
+ formatObjectsFixedWidth,
52
+ escapeRegex
53
+ };
@@ -0,0 +1,17 @@
1
+ const IP_ADDR = /^[1-9][0-9]{0,2}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/;
2
+ const schemaRegex = /^https?:\/\//;
3
+ const defaultSchema = 'https://';
4
+
5
+ function extractSubdomain(url) {
6
+ const hostname = new URL(url).hostname;
7
+ if (hostname === 'localhost' || IP_ADDR.test(hostname)) {
8
+ return undefined;
9
+ }
10
+ return hostname.split('.')[0];
11
+ }
12
+
13
+ module.exports = {
14
+ schemaRegex,
15
+ defaultSchema,
16
+ extractSubdomain
17
+ };
@@ -0,0 +1,14 @@
1
+ const Activate = require('./activate');
2
+ const AuthManager = require('./auth_manager');
3
+ const Extend = require('./extend');
4
+ const Question = require('./helper/question');
5
+ const { SettingsManager } = require('./settings_manager');
6
+
7
+ module.exports = {
8
+ extend: Extend.run,
9
+ activate: Activate.run,
10
+ login: AuthManager.loginAndUpdateSettings,
11
+ logout: AuthManager.logout,
12
+ question: Question,
13
+ config: SettingsManager.config
14
+ };