@coderich/sandman 0.0.3 → 0.0.5
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/index.js +5 -1
- package/package.json +2 -1
- package/src/ConfigClient.js +23 -18
- package/src/FetchService.js +79 -37
- package/src/Sandman.js +21 -20
- package/src/app.js +0 -5
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coderich/sandman",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"@coderich/config": "2.2.4",
|
|
19
19
|
"@coderich/util": "2.1.3",
|
|
20
20
|
"chokidar": "4.0.3",
|
|
21
|
+
"lodash.clonedeep": "4.5.0",
|
|
21
22
|
"lodash.merge": "4.6.2"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
package/src/ConfigClient.js
CHANGED
|
@@ -1,25 +1,30 @@
|
|
|
1
|
-
const FS = require('fs');
|
|
2
|
-
const Path = require('path');
|
|
1
|
+
// const FS = require('fs');
|
|
2
|
+
// const Path = require('path');
|
|
3
|
+
const cloneDeep = require('lodash.clonedeep');
|
|
3
4
|
const Config = require('@coderich/config');
|
|
4
5
|
|
|
5
6
|
module.exports = class ConfigClient extends Config {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const stat = FS.statSync(Path.join(parsed.dir, `${parsed.name}${parsed.ext}`));
|
|
10
|
-
if (stat?.isDirectory()) return false;
|
|
11
|
-
return !['.yml', '.yaml'].includes(parsed.ext.toLowerCase());
|
|
12
|
-
};
|
|
7
|
+
get(...args) {
|
|
8
|
+
return cloneDeep(super.get(...args));
|
|
9
|
+
}
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
// mergeConfigDir(dir) {
|
|
12
|
+
// const ignored = (parsed) => {
|
|
13
|
+
// if (parsed.name.startsWith('.')) return true;
|
|
14
|
+
// const stat = FS.statSync(Path.join(parsed.dir, `${parsed.name}${parsed.ext}`));
|
|
15
|
+
// if (stat?.isDirectory()) return false;
|
|
16
|
+
// return !['.yml', '.yaml'].includes(parsed.ext.toLowerCase());
|
|
17
|
+
// };
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
const path = paths.join('.');
|
|
18
|
-
if (!path.length) return prev.concat(data);
|
|
19
|
-
const indented = data.split('\n').map(line => (line.trim() ? ` ${line}` : line)).join('\n');
|
|
20
|
-
return prev.concat(`${path}:\n${indented}`);
|
|
21
|
-
}, '');
|
|
19
|
+
// const arr = Config.dirPaths(dir, ignored);
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
// const yaml = arr.reduce((prev, { paths, data }) => {
|
|
22
|
+
// const path = paths.join('.');
|
|
23
|
+
// if (!path.length) return prev.concat(data);
|
|
24
|
+
// const indented = data.split('\n').map(line => (line.trim() ? ` ${line}` : line)).join('\n');
|
|
25
|
+
// return prev.concat(`${path}:\n${indented}`);
|
|
26
|
+
// }, '');
|
|
27
|
+
|
|
28
|
+
// return this.merge(Config.parseYaml(yaml));
|
|
29
|
+
// }
|
|
25
30
|
};
|
package/src/FetchService.js
CHANGED
|
@@ -1,52 +1,49 @@
|
|
|
1
1
|
const Merge = require('lodash.merge');
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return { res, data };
|
|
11
|
-
}).then(resolve).catch(reject);
|
|
3
|
+
function shq(s) { return `'${String(s).replace(/'/g, `'"'"'`)}'`; }
|
|
4
|
+
|
|
5
|
+
exports.fetch = (request) => {
|
|
6
|
+
return fetch(request).then(async (response) => {
|
|
7
|
+
const ct = response.headers.get('content-type') || '';
|
|
8
|
+
const data = await (ct.includes('application/json') ? response.json() : response.text());
|
|
9
|
+
return { response, data };
|
|
12
10
|
});
|
|
13
11
|
};
|
|
14
12
|
|
|
15
13
|
exports.normalizeRequest = (req) => {
|
|
16
|
-
req.path ??= '';
|
|
17
|
-
req.
|
|
18
|
-
req.url += req.path;
|
|
19
|
-
req.url = Object.entries(req.params).reduce((url, [key, value]) => { url.searchParams.append(key, value); return url; }, new URL(req.url)).toString();
|
|
14
|
+
req.path ??= ''; req.method ??= 'get'; req.headers ??= {}; req.params ??= {};
|
|
15
|
+
req.url = Object.entries(req.params).reduce((url, [key, value]) => { url.searchParams.append(key, value); return url; }, new URL(`${req.url}${req.path}`)).toString();
|
|
20
16
|
req.headers = Object.entries(req.headers).reduce((prev, [key, value]) => Object.assign(prev, { [key.toLowerCase()]: value }), {});
|
|
21
17
|
const [contentType] = req.headers['content-type']?.split(';') || [];
|
|
22
|
-
const { data } = req; delete req.data;
|
|
23
|
-
if (data == null) return req;
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
19
|
+
if (req.data) {
|
|
20
|
+
switch (contentType) {
|
|
21
|
+
case 'application/json': {
|
|
22
|
+
req.body = JSON.stringify(req.data);
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
case 'application/x-www-form-urlencoded': {
|
|
26
|
+
req.body = new URLSearchParams(req.data).toString();
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
case 'multipart/form-data': {
|
|
30
|
+
delete req.headers['content-type'];
|
|
31
|
+
req.body = Object.entries(req.data).reduce((form, [key, value]) => {
|
|
32
|
+
form.append(key, value);
|
|
33
|
+
return form;
|
|
34
|
+
}, new FormData());
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
default: {
|
|
38
|
+
req.body = req.data;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
46
41
|
}
|
|
47
42
|
}
|
|
48
43
|
|
|
49
|
-
|
|
44
|
+
delete req.data; delete req.params;
|
|
45
|
+
const { url, ...params } = req;
|
|
46
|
+
return new Request(url, params);
|
|
50
47
|
};
|
|
51
48
|
|
|
52
49
|
exports.decorateRequest = (mergeData, key, request) => {
|
|
@@ -57,3 +54,48 @@ exports.decorateRequest = (mergeData, key, request) => {
|
|
|
57
54
|
|
|
58
55
|
return Merge({}, toMerge, request);
|
|
59
56
|
};
|
|
57
|
+
|
|
58
|
+
exports.toCURL = (request, { pretty = true, redactAuth = false } = {}) => {
|
|
59
|
+
if (!request) return '<no request>';
|
|
60
|
+
|
|
61
|
+
const { url, path, method, headers, params, data } = request;
|
|
62
|
+
|
|
63
|
+
const base = new URL(url);
|
|
64
|
+
const full = new URL(path || '', base);
|
|
65
|
+
|
|
66
|
+
// append query params
|
|
67
|
+
for (const [k, v] of Object.entries(params || {})) {
|
|
68
|
+
if (v == null) continue;
|
|
69
|
+
Array.isArray(v) ? v.forEach(x => full.searchParams.append(k, String(x))) : full.searchParams.append(k, String(v));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const parts = ['curl', '-sS'];
|
|
73
|
+
if (method) parts.push('-X', method.toUpperCase());
|
|
74
|
+
|
|
75
|
+
// headers
|
|
76
|
+
const hdrs = { ...headers };
|
|
77
|
+
for (const k of Object.keys(hdrs)) {
|
|
78
|
+
if (redactAuth && /^authorization$/i.test(k)) {
|
|
79
|
+
hdrs[k] = hdrs[k].replace(/(?<=^.{6}).+/, '***REDACTED***');
|
|
80
|
+
}
|
|
81
|
+
parts.push('-H', shq(`${k}: ${hdrs[k]}`));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// body (skip for GET)
|
|
85
|
+
if (data != null && !/^GET$/i.test(method)) {
|
|
86
|
+
if (typeof data === 'string') {
|
|
87
|
+
parts.push('--data-raw', shq(data));
|
|
88
|
+
} else if (data instanceof URLSearchParams) {
|
|
89
|
+
parts.push('-H', shq('Content-Type: application/x-www-form-urlencoded'));
|
|
90
|
+
parts.push('--data', shq(data.toString()));
|
|
91
|
+
} else if (typeof data === 'object') {
|
|
92
|
+
// JSON by default
|
|
93
|
+
const hasCT = Object.keys(hdrs).some(h => /^content-type$/i.test(h));
|
|
94
|
+
if (!hasCT) parts.push('-H', shq('Content-Type: application/json'));
|
|
95
|
+
parts.push('--data-raw', shq(JSON.stringify(data)));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
parts.push(shq(full.toString()));
|
|
100
|
+
return pretty ? parts.join(' \\\n ') : parts.join(' ');
|
|
101
|
+
};
|
package/src/Sandman.js
CHANGED
|
@@ -10,8 +10,8 @@ const ConfigClient = require('./ConfigClient');
|
|
|
10
10
|
const resolveSymbol = Symbol('resolve');
|
|
11
11
|
|
|
12
12
|
module.exports = class Sandman extends EventEmitter {
|
|
13
|
-
#configClient; #configDir; #options; #watcher; #readline; #mergeData = {}; #cli;
|
|
14
|
-
#captureCandidates = false; #candidates = []; #tabCounter = 0; #candidateIndex
|
|
13
|
+
#configClient; #configDir; #options; #watcher; #readline; #mergeData = {}; #cli; #cliCounter = 0;
|
|
14
|
+
#captureCandidates = false; #candidates = []; #tabCounter = 0; #candidateIndex; #line; #lastToken;
|
|
15
15
|
|
|
16
16
|
constructor(configDir, options) {
|
|
17
17
|
super();
|
|
@@ -37,7 +37,6 @@ module.exports = class Sandman extends EventEmitter {
|
|
|
37
37
|
resolve: {
|
|
38
38
|
value: (...args) => {
|
|
39
39
|
this.#configClient.resolve(...args);
|
|
40
|
-
this.#prompt();
|
|
41
40
|
return this.#cli;
|
|
42
41
|
},
|
|
43
42
|
configurable: true,
|
|
@@ -48,22 +47,24 @@ module.exports = class Sandman extends EventEmitter {
|
|
|
48
47
|
#run(key) {
|
|
49
48
|
const api = this.#get(key, {});
|
|
50
49
|
if (!api?.request) return this.emit('error', { key, error: `Request "${key}" Not Found` });
|
|
51
|
-
this.emit('
|
|
50
|
+
this.emit('api', { api, key });
|
|
51
|
+
const request = FetchService.normalizeRequest(api.request);
|
|
52
|
+
this.emit('request', { request, api, key });
|
|
52
53
|
|
|
53
|
-
return FetchService.fetch(
|
|
54
|
-
this.emit('response', {
|
|
55
|
-
return {
|
|
54
|
+
return FetchService.fetch(request).then(({ response, data }) => {
|
|
55
|
+
this.emit('response', { response, api, key, data });
|
|
56
|
+
return { response, api, key, data };
|
|
56
57
|
}).catch((error) => {
|
|
57
58
|
this.emit('error', { key, api, error });
|
|
58
59
|
return Promise.reject(error);
|
|
59
60
|
}).then((results) => {
|
|
60
|
-
return results.
|
|
61
|
+
return results.response.ok ? results : Promise.reject(results);
|
|
61
62
|
});
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
#prompt() {
|
|
65
66
|
this.#readline.setPrompt(this.#configClient.get('prompt'));
|
|
66
|
-
this.#readline.prompt();
|
|
67
|
+
this.#readline.prompt(true);
|
|
67
68
|
return this;
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -89,6 +90,7 @@ module.exports = class Sandman extends EventEmitter {
|
|
|
89
90
|
get: (...args) => this.#get(...args),
|
|
90
91
|
set: (key = '', value = null) => this.#configClient.set(key, value),
|
|
91
92
|
del: (key = '') => this.#configClient.del(key),
|
|
93
|
+
curl: key => FetchService.toCURL(this.#get(key, {}).request),
|
|
92
94
|
quit: () => process.exit(),
|
|
93
95
|
}, {
|
|
94
96
|
get(obj, prop, receiver) {
|
|
@@ -96,8 +98,12 @@ module.exports = class Sandman extends EventEmitter {
|
|
|
96
98
|
|
|
97
99
|
if (typeof value === 'function') {
|
|
98
100
|
return (...args) => {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
self.#cliCounter++;
|
|
102
|
+
self.#readline.pause();
|
|
103
|
+
const result = value.apply(this, args);
|
|
104
|
+
Promise.resolve(result).catch(() => null).finally(() => setImmediate(() => {
|
|
105
|
+
if (--self.#cliCounter === 0) self.#prompt();
|
|
106
|
+
}));
|
|
101
107
|
return result;
|
|
102
108
|
};
|
|
103
109
|
}
|
|
@@ -154,7 +160,8 @@ module.exports = class Sandman extends EventEmitter {
|
|
|
154
160
|
});
|
|
155
161
|
|
|
156
162
|
const candidates = Array.from(new Set(startsWithCandidates.concat(requestKeyCandidates)));
|
|
157
|
-
if (this.#captureCandidates) { this.#candidates = candidates; this.#line = line; this.#lastToken = lastToken; }
|
|
163
|
+
if (this.#captureCandidates) { this.#candidates = candidates; this.#line = line; this.#lastToken = lastToken; this.#candidateIndex = -1; }
|
|
164
|
+
if (candidates.length === 1 && candidates[0] === lastToken) return [[], lastToken];
|
|
158
165
|
return [candidates, lastToken];
|
|
159
166
|
},
|
|
160
167
|
});
|
|
@@ -169,14 +176,8 @@ module.exports = class Sandman extends EventEmitter {
|
|
|
169
176
|
Readline.clearLine(process.stdout, 0);
|
|
170
177
|
this.#readline.prompt();
|
|
171
178
|
} else if (this.#tabCounter > 2 && this.#candidates.length) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (!value) {
|
|
175
|
-
this.#candidateIndex = 0;
|
|
176
|
-
value = this.#candidates.at(this.#candidateIndex++);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
value = this.#line.replace(this.#lastToken, value);
|
|
179
|
+
const candidate = this.#candidates[this.#candidateIndex = ++this.#candidateIndex % this.#candidates.length];
|
|
180
|
+
const value = this.#line.replace(this.#lastToken, candidate);
|
|
180
181
|
this.#readline.line = value;
|
|
181
182
|
this.#readline.cursor = value.length;
|
|
182
183
|
this.#readline.prompt(true);
|