@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.
- package/CHANGELOG.md +24 -0
- package/bin/activate.js +1 -2
- package/bin/deploy/index.js +1 -1
- package/bin/deploy/to-sqlite.js +2 -2
- package/bin/extend.js +2 -4
- package/bin/login.js +7 -8
- package/bin/logout.js +1 -2
- package/bin/repl.js +1 -1
- package/bin/watch.js +12 -8
- package/lib/client/activate.js +243 -0
- package/lib/client/auth_manager.js +115 -0
- package/lib/client/base_command.js +55 -0
- package/lib/client/extend.js +179 -0
- package/lib/client/helper/cf_helper.js +186 -0
- package/lib/client/helper/dependency_helper.js +26 -0
- package/lib/client/helper/env_helper.js +4 -0
- package/lib/client/helper/fs_helper.js +83 -0
- package/lib/client/helper/logging.js +37 -0
- package/lib/client/helper/question.js +50 -0
- package/lib/client/helper/string_helper.js +53 -0
- package/lib/client/helper/url_helper.js +17 -0
- package/lib/client/index.js +14 -0
- package/lib/client/params.js +435 -0
- package/lib/client/settings_manager.js +526 -0
- package/lib/import/index.js +5 -18
- package/lib/import/v2parser.js +282 -27
- package/lib/import/v4parser.js +341 -54
- package/lib/init/constants.js +1 -1
- package/lib/init/index.js +13 -10
- package/lib/init/template/data/index.js +38 -46
- package/lib/init/template/hana/index.js +2 -2
- package/lib/init/template/lint/index.js +8 -6
- package/lib/init/template/mta/projectReader.js +8 -5
- package/lib/init/template/nodejs/files/.vscode/extensions.json +1 -1
- package/lib/init/template/nodejs/index.js +1 -8
- package/lib/lint/Linter.js +62 -60
- package/lib/lint/io.js +9 -33
- package/npm-shrinkwrap.json +7684 -12883
- package/package.json +4 -5
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { fse } = require('@sap/cds-foss');
|
|
4
|
+
|
|
5
|
+
const AuthManager = require('./auth_manager');
|
|
6
|
+
const BaseCommand = require('./base_command');
|
|
7
|
+
const { normalizePath, isParent } = require('./helper/fs_helper');
|
|
8
|
+
const Logger = require('./helper/logging');
|
|
9
|
+
const Question = require('./helper/question');
|
|
10
|
+
|
|
11
|
+
const COMMAND_URL_SUFFIX = 'content';
|
|
12
|
+
const CUSTOM_FILE_FOLDERS = ['wsdl'];
|
|
13
|
+
const DB_FOLDER = 'db';
|
|
14
|
+
const SRV_FOLDER = 'srv';
|
|
15
|
+
const TEMPLATE_FOLDER = 'tpl';
|
|
16
|
+
|
|
17
|
+
function folderExists(folder) {
|
|
18
|
+
return `${folder} already contains an extension project, use another directory or use --force.` +
|
|
19
|
+
'\nWhen using --force, make sure you are using a version control system (like git) to avoid data loss';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = class Extend extends BaseCommand {
|
|
23
|
+
|
|
24
|
+
static get COMMAND_URL_SUFFIX() { return COMMAND_URL_SUFFIX; }
|
|
25
|
+
|
|
26
|
+
static async storeFiles(content, targetDirectory, filter) {
|
|
27
|
+
if (!fse.existsSync(targetDirectory)) {
|
|
28
|
+
fse.mkdirpSync(targetDirectory);
|
|
29
|
+
}
|
|
30
|
+
targetDirectory = fse.realpathSync(targetDirectory)
|
|
31
|
+
const writtenFiles = new Set();
|
|
32
|
+
const filesToWrite = new Map();
|
|
33
|
+
for (const [filename, filecontent] of content) {
|
|
34
|
+
const filepath = path.join(targetDirectory, filename);
|
|
35
|
+
if (filepath.startsWith(targetDirectory) && (!filter || await filter(filepath))) {
|
|
36
|
+
filesToWrite.set(filepath, filecontent);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
for (const [filepath, content] of [...filesToWrite.entries()]) {
|
|
40
|
+
const normalizedFilepath = normalizePath(filepath);
|
|
41
|
+
fse.mkdirpSync(path.dirname(normalizedFilepath));
|
|
42
|
+
fse.writeFileSync(normalizedFilepath, content);
|
|
43
|
+
writtenFiles.add(path.relative(process.cwd(), normalizedFilepath));
|
|
44
|
+
Extend._logWrittenFile(normalizedFilepath);
|
|
45
|
+
}
|
|
46
|
+
return writtenFiles;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static async storeCustomFiles(directory, customFiles) {
|
|
50
|
+
const customFileMap = new Map();
|
|
51
|
+
customFiles.forEach(entry => {
|
|
52
|
+
if (entry.category === 'wsdl') {
|
|
53
|
+
const normalizedFilename = normalizePath(entry.name);
|
|
54
|
+
const folder = normalizedFilename.split(path.sep)[0];
|
|
55
|
+
if (CUSTOM_FILE_FOLDERS.includes(folder)) {
|
|
56
|
+
customFileMap.set(entry.name, entry.content);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
await Extend.storeFiles([...customFileMap], directory);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static async storeTemplateFiles(templates, directory) {
|
|
64
|
+
await Extend.storeFiles(templates, directory, async filepath => {
|
|
65
|
+
const normalizedPath = normalizePath(filepath);
|
|
66
|
+
if (!isParent(path.join(directory, DB_FOLDER), normalizedPath) && !isParent(path.join(directory, SRV_FOLDER), normalizedPath)) {
|
|
67
|
+
Logger.log(`Ignoring template ${normalizedPath}: not in ${DB_FOLDER} or ${SRV_FOLDER}`);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static async run(paramValues) {
|
|
75
|
+
try {
|
|
76
|
+
const params = await AuthManager.loginAndUpdateSettings(paramValues);
|
|
77
|
+
|
|
78
|
+
if (process.env.DEBUG) {
|
|
79
|
+
Logger.log(`\n\nCreating extension project with parameters ${JSON.stringify({
|
|
80
|
+
appUrl: params.get('appUrl'),
|
|
81
|
+
projectFolder: params.get('projectFolder'),
|
|
82
|
+
subdomain: params.get('subdomain'),
|
|
83
|
+
passcode: params.get('passcode'),
|
|
84
|
+
username: params.get('username'),
|
|
85
|
+
password: params.get('password') ? '***' : 'undefined'
|
|
86
|
+
})}`);
|
|
87
|
+
} else {
|
|
88
|
+
Logger.log(`\n\nCreating extension project for ${params.get('appUrl')}, subdomain '${params.get('subdomain')}', in folder '${params.get('projectFolder')}'`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const runOnExistingProject = await Extend.checkProject(params.get('projectFolder'));
|
|
92
|
+
if (runOnExistingProject && !(params.get('force') || params.get('templates'))) {
|
|
93
|
+
throw new Error(folderExists(params.get('projectFolder')));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
await Extend.runExtend(params);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
await Extend.handleHttpError(error, params);
|
|
100
|
+
}
|
|
101
|
+
} catch (mainError) {
|
|
102
|
+
Logger.error(undefined, mainError);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static async runExtend(params) {
|
|
107
|
+
const extendUrl = Extend.getMtxApiUrl(params.get('appUrl'), COMMAND_URL_SUFFIX);
|
|
108
|
+
const requestOptions = Object.assign({
|
|
109
|
+
method: 'GET',
|
|
110
|
+
retry: 0
|
|
111
|
+
}, params.get('reqAuth'));
|
|
112
|
+
|
|
113
|
+
Logger.log('Retrieving files...');
|
|
114
|
+
|
|
115
|
+
const [ response ] = (await axios(extendUrl, requestOptions)).data;
|
|
116
|
+
|
|
117
|
+
Logger.log('\nWriting files to: ' + params.get('projectFolder'));
|
|
118
|
+
|
|
119
|
+
if (params.get('templates')) {
|
|
120
|
+
await Extend.storeTemplateFilesToTpl(response.templates, params.get('projectFolder'));
|
|
121
|
+
return; // only write templates
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const content = response.base.concat(response.extension);
|
|
125
|
+
|
|
126
|
+
await Extend.storeFiles(content, params.get('projectFolder'));
|
|
127
|
+
|
|
128
|
+
await fse.mkdirp(path.join(params.get('projectFolder'), DB_FOLDER));
|
|
129
|
+
await fse.mkdirp(path.join(params.get('projectFolder'), SRV_FOLDER));
|
|
130
|
+
|
|
131
|
+
Logger.log(`\nPut database extension models into ${DB_FOLDER} folder.`);
|
|
132
|
+
Logger.log(`Put service extension models into ${SRV_FOLDER} folder.`);
|
|
133
|
+
|
|
134
|
+
if (response.templates && response.templates.length > 0) {
|
|
135
|
+
// only write templates if there are no extensions so far
|
|
136
|
+
if (response.extension && response.extension.length === 0) {
|
|
137
|
+
Logger.log(`Writing template files to ${DB_FOLDER} and ${SRV_FOLDER} folder`);
|
|
138
|
+
await Extend.storeTemplateFiles(response.templates, params.get('projectFolder'));
|
|
139
|
+
} else {
|
|
140
|
+
Logger.log('Ignoring templates because extension is already active.');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (params.get('wsdl')) {
|
|
145
|
+
|
|
146
|
+
Logger.log('Writing custom files');
|
|
147
|
+
|
|
148
|
+
const customFileUrl = Extend.getMtxApiUrl(params.get('appUrl'), Extend.CUSTOM_FILE_URL_SUFFIX);
|
|
149
|
+
const customFiles = (await axios(customFileUrl, params)).data;
|
|
150
|
+
await Extend.storeCustomFiles(params.get('projectFolder'), customFiles);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static async storeTemplateFilesToTpl(templates, projectFolder) {
|
|
155
|
+
if (templates && templates.length > 0) {
|
|
156
|
+
const targetDirectory = path.join(projectFolder, TEMPLATE_FOLDER);
|
|
157
|
+
const targetExists = await fse.pathExists(targetDirectory);
|
|
158
|
+
if (!targetExists || await this.promptTemplateOverwrite()) {
|
|
159
|
+
if (targetExists) {
|
|
160
|
+
await fse.remove(targetDirectory);
|
|
161
|
+
}
|
|
162
|
+
Logger.log(`Writing template files to ${TEMPLATE_FOLDER} folder`);
|
|
163
|
+
await Extend.storeTemplateFiles(templates, targetDirectory);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
} else {
|
|
167
|
+
Logger.log('No templates available');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
static promptTemplateOverwrite() {
|
|
172
|
+
return Question.askBooleanQuestion(`This will overwrite extisting templates in folder ${TEMPLATE_FOLDER}. Continue (yN)? `);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
static _logWrittenFile(file) {
|
|
176
|
+
Logger.log('CREATE: ' + file);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
}
|
|
@@ -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,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
|
+
};
|