cloudcmd 17.1.6 → 17.2.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 +10 -1
- package/HELP.md +3 -2
- package/README.md +1 -1
- package/dist/sw.js +1 -1
- package/dist-dev/sw.js +1 -1
- package/package.json +3 -2
- package/server/{cloudcmd.js → cloudcmd.mjs} +52 -48
- package/server/cloudcmd.spec.mjs +180 -0
- package/server/config.spec.mjs +105 -0
- package/server/distribute/export.spec.mjs +68 -0
- package/server/distribute/import.spec.mjs +287 -0
- package/server/markdown/index.spec.mjs +126 -0
- package/server/rest/index.js +21 -18
- package/server/route.spec.mjs +458 -0
- package/server/server.mjs +6 -6
- package/server/terminal.js +6 -2
- package/server/terminal.spec.mjs +73 -0
- package/server/{user-menu.js → user-menu.mjs} +17 -17
- package/server/user-menu.spec.mjs +74 -0
- package/server/validate.spec.mjs +104 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
import path, {dirname} from 'node:path';
|
|
2
|
+
import {fileURLToPath} from 'node:url';
|
|
3
|
+
import {Readable} from 'node:stream';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import tryToCatch from 'try-to-catch';
|
|
6
|
+
import {test, stub} from 'supertape';
|
|
7
|
+
import serveOnce from 'serve-once';
|
|
8
|
+
import cloudcmd from './cloudcmd.mjs';
|
|
9
|
+
import {_getReadDir} from './route.js';
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
const fixtureDir = path.join(__dirname, '..', 'test', 'fixture');
|
|
14
|
+
const {createConfigManager} = cloudcmd;
|
|
15
|
+
|
|
16
|
+
const defaultConfig = {
|
|
17
|
+
auth: false,
|
|
18
|
+
dropbox: false,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const {request} = serveOnce(cloudcmd, {
|
|
22
|
+
config: defaultConfig,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const {stringify} = JSON;
|
|
26
|
+
const {assign} = Object;
|
|
27
|
+
|
|
28
|
+
test('cloudcmd: route: buttons: no console', async (t) => {
|
|
29
|
+
const options = {
|
|
30
|
+
config: {
|
|
31
|
+
console: false,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const {body} = await request.get('/', {
|
|
36
|
+
options,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
t.match(body, 'icon-console none', 'should hide console');
|
|
40
|
+
t.end();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('cloudcmd: route: buttons: console', async (t) => {
|
|
44
|
+
const config = {
|
|
45
|
+
console: true,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const options = {
|
|
49
|
+
config,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const {body} = await request.get('/', {
|
|
53
|
+
options,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
t.notOk(/icon-console none/.test(body), 'should not hide console');
|
|
57
|
+
t.end();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('cloudcmd: route: buttons: no config', async (t) => {
|
|
61
|
+
const config = {
|
|
62
|
+
configDialog: false,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const options = {
|
|
66
|
+
config,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const {body} = await request.get('/', {
|
|
70
|
+
options,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
t.match(body, 'icon-config none', 'should hide config');
|
|
74
|
+
t.end();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('cloudcmd: route: buttons: no contact', async (t) => {
|
|
78
|
+
const config = {
|
|
79
|
+
contact: false,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const options = {
|
|
83
|
+
config,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const {body} = await request.get('/', {
|
|
87
|
+
options,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
t.match(body, 'icon-contact none', 'should hide contact');
|
|
91
|
+
t.end();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('cloudcmd: route: buttons: one file panel: move', async (t) => {
|
|
95
|
+
const config = {
|
|
96
|
+
oneFilePanel: true,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const options = {
|
|
100
|
+
config,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const {body} = await request.get('/', {
|
|
104
|
+
options,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
t.match(body, 'icon-move none', 'should hide move button');
|
|
108
|
+
t.end();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('cloudcmd: route: buttons: one file panel: copy', async (t) => {
|
|
112
|
+
const config = {
|
|
113
|
+
oneFilePanel: true,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const options = {
|
|
117
|
+
config,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const {body} = await request.get('/', {
|
|
121
|
+
options,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
t.match(body, 'icon-copy none', 'should hide copy button');
|
|
125
|
+
t.end();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('cloudcmd: route: keys panel: hide', async (t) => {
|
|
129
|
+
const config = {
|
|
130
|
+
keysPanel: false,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const options = {
|
|
134
|
+
config,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const {body} = await request.get('/', {
|
|
138
|
+
options,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
t.match(body, 'keyspanel hidden', 'should hide keyspanel');
|
|
142
|
+
t.end();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('cloudcmd: route: keys panel', async (t) => {
|
|
146
|
+
const config = {
|
|
147
|
+
keysPanel: true,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const options = {
|
|
151
|
+
config,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const {body} = await request.get('/', {
|
|
155
|
+
options,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
t.notOk(/keyspanel hidden/.test(body), 'should show keyspanel');
|
|
159
|
+
t.end();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('cloudcmd: route: symlink', async (t) => {
|
|
163
|
+
const emptyDir = path.join(fixtureDir, 'empty-dir');
|
|
164
|
+
const root = fixtureDir;
|
|
165
|
+
const symlink = path.join(root, 'symlink-dir');
|
|
166
|
+
|
|
167
|
+
const config = {
|
|
168
|
+
root,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const options = {
|
|
172
|
+
config,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
fs.symlinkSync(emptyDir, symlink);
|
|
176
|
+
|
|
177
|
+
const {body} = await request.get('/fs/symlink-dir', {
|
|
178
|
+
options,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
fs.unlinkSync(symlink);
|
|
182
|
+
|
|
183
|
+
t.ok(body.length, 'should return html document');
|
|
184
|
+
t.end();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('cloudcmd: route: not found', async (t) => {
|
|
188
|
+
const root = fixtureDir;
|
|
189
|
+
const config = {
|
|
190
|
+
root,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const options = {
|
|
194
|
+
config,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const {body} = await request.get('/fs/file-not-found', {
|
|
198
|
+
options,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
t.match(body, 'ENOENT: no such file or directory', 'should return error');
|
|
202
|
+
t.end();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('cloudcmd: route: sendIndex: encode', async (t) => {
|
|
206
|
+
const name = '"><svg onload=alert(3);>';
|
|
207
|
+
const nameEncoded = '"><svg onload=alert(3);>';
|
|
208
|
+
const path = '/';
|
|
209
|
+
|
|
210
|
+
const files = [{
|
|
211
|
+
name,
|
|
212
|
+
}];
|
|
213
|
+
|
|
214
|
+
const stream = Readable.from(stringify({
|
|
215
|
+
path,
|
|
216
|
+
files,
|
|
217
|
+
}));
|
|
218
|
+
|
|
219
|
+
assign(stream, {
|
|
220
|
+
path,
|
|
221
|
+
files,
|
|
222
|
+
type: 'directory',
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const read = stub().resolves(stream);
|
|
226
|
+
|
|
227
|
+
cloudcmd.depStore('win32', {
|
|
228
|
+
read,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const {request} = serveOnce(cloudcmd, {
|
|
232
|
+
configManager: createConfigManager(),
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const {body} = await request.get('/');
|
|
236
|
+
|
|
237
|
+
cloudcmd.depStore();
|
|
238
|
+
|
|
239
|
+
t.match(body, nameEncoded, 'should encode name');
|
|
240
|
+
t.end();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('cloudcmd: route: sendIndex: encode: not encoded', async (t) => {
|
|
244
|
+
const name = '"><svg onload=alert(3);>';
|
|
245
|
+
const path = '/';
|
|
246
|
+
const files = [{
|
|
247
|
+
name,
|
|
248
|
+
}];
|
|
249
|
+
|
|
250
|
+
const stream = Readable.from(stringify({
|
|
251
|
+
path,
|
|
252
|
+
files,
|
|
253
|
+
}));
|
|
254
|
+
|
|
255
|
+
assign(stream, {
|
|
256
|
+
path,
|
|
257
|
+
files,
|
|
258
|
+
type: 'directory',
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const read = stub().resolves(stream);
|
|
262
|
+
|
|
263
|
+
cloudcmd.depStore('win32', {
|
|
264
|
+
read,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const {request} = serveOnce(cloudcmd);
|
|
268
|
+
const {body} = await request.get('/');
|
|
269
|
+
|
|
270
|
+
cloudcmd.depStore();
|
|
271
|
+
|
|
272
|
+
t.notOk(body.includes(name), 'should not put not encoded name');
|
|
273
|
+
t.end();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test('cloudcmd: route: sendIndex: ddos: render', async (t) => {
|
|
277
|
+
const name = `$$$'"`;
|
|
278
|
+
const path = '/';
|
|
279
|
+
const files = [{
|
|
280
|
+
name,
|
|
281
|
+
}];
|
|
282
|
+
|
|
283
|
+
const stream = Readable.from(stringify({
|
|
284
|
+
path,
|
|
285
|
+
files,
|
|
286
|
+
}));
|
|
287
|
+
|
|
288
|
+
assign(stream, {
|
|
289
|
+
path,
|
|
290
|
+
files,
|
|
291
|
+
type: 'directory',
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const read = stub().resolves(stream);
|
|
295
|
+
|
|
296
|
+
cloudcmd.depStore('win32', {
|
|
297
|
+
read,
|
|
298
|
+
});
|
|
299
|
+
const {request} = serveOnce(cloudcmd, {
|
|
300
|
+
config: defaultConfig,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const {status} = await request.get('/');
|
|
304
|
+
|
|
305
|
+
cloudcmd.depStore();
|
|
306
|
+
|
|
307
|
+
t.equal(status, 200, 'should not hang up');
|
|
308
|
+
t.end();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('cloudcmd: route: buttons: no terminal', async (t) => {
|
|
312
|
+
const config = {
|
|
313
|
+
terminal: false,
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const options = {
|
|
317
|
+
config,
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const {body} = await request.get('/', {
|
|
321
|
+
options,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
t.match(body, 'icon-terminal none', 'should hide terminal');
|
|
325
|
+
t.end();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test('cloudcmd: route: no termianl: /fs', async (t) => {
|
|
329
|
+
const config = {
|
|
330
|
+
terminal: false,
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const options = {
|
|
334
|
+
config,
|
|
335
|
+
configManager: createConfigManager(),
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const {request} = serveOnce(cloudcmd);
|
|
339
|
+
|
|
340
|
+
const {body} = await request.get('/fs', {
|
|
341
|
+
options,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
t.match(body, 'icon-terminal none', 'should hide terminal');
|
|
345
|
+
t.end();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test('cloudcmd: route: buttons: terminal: can not load', async (t) => {
|
|
349
|
+
const config = {
|
|
350
|
+
terminal: true,
|
|
351
|
+
terminalPath: 'xxxxxxxxxxxx',
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const options = {
|
|
355
|
+
config,
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const {body} = await request.get('/', {
|
|
359
|
+
options,
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
t.match(body, 'icon-terminal none', 'should not enable terminal');
|
|
363
|
+
t.end();
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('cloudcmd: route: buttons: terminal', async (t) => {
|
|
367
|
+
const config = {
|
|
368
|
+
terminal: true,
|
|
369
|
+
terminalPath: 'console-io',
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const options = {
|
|
373
|
+
config,
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const {body} = await request.get('/', {
|
|
377
|
+
options,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
t.notOk(/icon-terminal none/.test(body), 'should not enable terminal');
|
|
381
|
+
t.end();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test('cloudcmd: route: buttons: contact', async (t) => {
|
|
385
|
+
const config = {
|
|
386
|
+
contact: true,
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const options = {
|
|
390
|
+
config,
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const {request} = serveOnce(cloudcmd);
|
|
394
|
+
|
|
395
|
+
const {body} = await request.get('/', {
|
|
396
|
+
options,
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
t.notOk(/icon-contact none/.test(body), 'should enable terminal');
|
|
400
|
+
t.end();
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test('cloudcmd: route: dropbox', async (t) => {
|
|
404
|
+
const config = createConfigManager();
|
|
405
|
+
config('dropbox', true);
|
|
406
|
+
config('dropboxToken', '');
|
|
407
|
+
|
|
408
|
+
const readdir = _getReadDir(config);
|
|
409
|
+
const [e] = await tryToCatch(readdir, '/root');
|
|
410
|
+
|
|
411
|
+
t.match(e.message, 'API', 'should contain word token in message');
|
|
412
|
+
t.end();
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test('cloudcmd: route: content length', async (t) => {
|
|
416
|
+
const options = {
|
|
417
|
+
root: fixtureDir,
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const {headers} = await request.get('/route.js', {
|
|
421
|
+
options,
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
const result = headers.get('content-length');
|
|
425
|
+
|
|
426
|
+
t.ok(result);
|
|
427
|
+
t.end();
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test('cloudcmd: route: read: root', async (t) => {
|
|
431
|
+
const stream = Readable.from('hello');
|
|
432
|
+
|
|
433
|
+
stream.contentLength = 5;
|
|
434
|
+
|
|
435
|
+
const read = stub().returns(stream);
|
|
436
|
+
cloudcmd.depStore('win32', {
|
|
437
|
+
read,
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
const configManager = createConfigManager();
|
|
441
|
+
const root = '/hello';
|
|
442
|
+
|
|
443
|
+
configManager('root', root);
|
|
444
|
+
|
|
445
|
+
const {request} = serveOnce(cloudcmd, {
|
|
446
|
+
configManager,
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
await request.get('/fs/route.js');
|
|
450
|
+
cloudcmd.depStore();
|
|
451
|
+
|
|
452
|
+
const expected = ['/hello/route.js', {
|
|
453
|
+
root,
|
|
454
|
+
}];
|
|
455
|
+
|
|
456
|
+
t.calledWith(read, expected);
|
|
457
|
+
t.end();
|
|
458
|
+
});
|
package/server/server.mjs
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import cloudcmd from './cloudcmd.js';
|
|
2
1
|
import http from 'node:http';
|
|
3
2
|
import {promisify} from 'node:util';
|
|
3
|
+
import process from 'node:process';
|
|
4
4
|
import currify from 'currify';
|
|
5
5
|
import squad from 'squad';
|
|
6
6
|
import tryToCatch from 'try-to-catch';
|
|
7
|
-
import wraptile from 'wraptile';
|
|
8
|
-
import compression from 'compression';
|
|
9
|
-
import threadIt from 'thread-it';
|
|
10
|
-
import exit from './exit.js';
|
|
11
7
|
import opn from 'open';
|
|
12
8
|
import express from 'express';
|
|
13
9
|
import {Server} from 'socket.io';
|
|
14
10
|
import tryRequire from 'tryrequire';
|
|
15
|
-
import
|
|
11
|
+
import wraptile from 'wraptile';
|
|
12
|
+
import compression from 'compression';
|
|
13
|
+
import threadIt from 'thread-it';
|
|
14
|
+
import exit from './exit.js';
|
|
15
|
+
import cloudcmd from './cloudcmd.mjs';
|
|
16
16
|
|
|
17
17
|
const bind = (f, self) => f.bind(self);
|
|
18
18
|
|
package/server/terminal.js
CHANGED
|
@@ -8,11 +8,15 @@ const noop = (req, res, next) => {
|
|
|
8
8
|
|
|
9
9
|
noop.listen = noop;
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
function _getModule(a) {
|
|
12
|
+
return require(a);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = (config, arg, {getModule = _getModule} = {}) => {
|
|
12
16
|
if (!config('terminal'))
|
|
13
17
|
return noop;
|
|
14
18
|
|
|
15
|
-
const [e, terminalModule] = tryCatch(
|
|
19
|
+
const [e, terminalModule] = tryCatch(getModule, config('terminalPath'));
|
|
16
20
|
|
|
17
21
|
if (!e && !arg)
|
|
18
22
|
return terminalModule;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {test, stub} from 'supertape';
|
|
2
|
+
import terminal from './terminal.js';
|
|
3
|
+
import {createConfigManager} from './cloudcmd.mjs';
|
|
4
|
+
|
|
5
|
+
test('cloudcmd: terminal: disabled', (t) => {
|
|
6
|
+
const config = createConfigManager();
|
|
7
|
+
config('terminal', false);
|
|
8
|
+
|
|
9
|
+
const fn = terminal(config);
|
|
10
|
+
|
|
11
|
+
t.notOk(fn(), 'should return noop');
|
|
12
|
+
t.end();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('cloudcmd: terminal: disabled: listen', (t) => {
|
|
16
|
+
const config = createConfigManager();
|
|
17
|
+
config('terminal', false);
|
|
18
|
+
|
|
19
|
+
const fn = terminal(config).listen();
|
|
20
|
+
|
|
21
|
+
t.notOk(fn, 'should return noop');
|
|
22
|
+
t.end();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('cloudcmd: terminal: enabled', (t) => {
|
|
26
|
+
const term = stub();
|
|
27
|
+
const arg = 'hello';
|
|
28
|
+
const config = stub().returns(true);
|
|
29
|
+
const getModule = stub().returns(term);
|
|
30
|
+
|
|
31
|
+
terminal(config, arg, {
|
|
32
|
+
getModule,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
t.calledWith(term, [arg], 'should call terminal');
|
|
36
|
+
t.end();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('cloudcmd: terminal: enabled: no string', (t) => {
|
|
40
|
+
const {log: originalLog} = console;
|
|
41
|
+
const log = stub();
|
|
42
|
+
|
|
43
|
+
console.log = log;
|
|
44
|
+
const config = createConfigManager();
|
|
45
|
+
|
|
46
|
+
config('terminal', true);
|
|
47
|
+
config('terminalPath', 'hello');
|
|
48
|
+
terminal(config);
|
|
49
|
+
|
|
50
|
+
console.log = originalLog;
|
|
51
|
+
|
|
52
|
+
const msg = `cloudcmd --terminal: Cannot find module 'hello'`;
|
|
53
|
+
const [arg] = log.args[0];
|
|
54
|
+
|
|
55
|
+
t.match(arg, RegExp(msg), 'should call with msg');
|
|
56
|
+
t.end();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('cloudcmd: terminal: no arg', (t) => {
|
|
60
|
+
const gritty = {};
|
|
61
|
+
const getModule = stub().returns(gritty);
|
|
62
|
+
const config = createConfigManager();
|
|
63
|
+
|
|
64
|
+
config('terminal', true);
|
|
65
|
+
config('terminalPath', 'gritty');
|
|
66
|
+
|
|
67
|
+
const result = terminal(config, '', {
|
|
68
|
+
getModule,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
t.equal(result, gritty);
|
|
72
|
+
t.end();
|
|
73
|
+
});
|
|
@@ -1,26 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
import {createRequire} from 'node:module';
|
|
2
|
+
import {homedir} from 'node:os';
|
|
3
|
+
import {readFile as _readFile} from 'node:fs/promises';
|
|
4
|
+
import {join} from 'node:path';
|
|
5
|
+
import montag from 'montag';
|
|
6
|
+
import tryToCatch from 'try-to-catch';
|
|
7
|
+
import currify from 'currify';
|
|
8
|
+
import threadIt from 'thread-it';
|
|
9
|
+
import {codeframe} from 'putout';
|
|
2
10
|
|
|
3
|
-
const
|
|
4
|
-
const {readFile} = require('node:fs/promises');
|
|
5
|
-
|
|
6
|
-
const {join} = require('node:path');
|
|
7
|
-
|
|
8
|
-
const montag = require('montag');
|
|
9
|
-
const tryToCatch = require('try-to-catch');
|
|
10
|
-
const currify = require('currify');
|
|
11
|
-
const threadIt = require('thread-it');
|
|
12
|
-
const {codeframe} = require('putout');
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
13
12
|
const putout = threadIt(require.resolve('putout'));
|
|
14
13
|
|
|
15
14
|
threadIt.init();
|
|
16
15
|
// warm up worker cache
|
|
17
16
|
transpile('');
|
|
18
17
|
|
|
19
|
-
const
|
|
20
|
-
const DEFAULT_MENU_PATH =
|
|
18
|
+
const PREFIX = '/api/v1/user-menu';
|
|
19
|
+
const DEFAULT_MENU_PATH = new URL('../static/user-menu.js', import.meta.url).pathname;
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
if (req.url.
|
|
21
|
+
export default currify(async ({menuName, readFile = _readFile}, req, res, next) => {
|
|
22
|
+
if (!req.url.startsWith(PREFIX))
|
|
24
23
|
return next();
|
|
25
24
|
|
|
26
25
|
const {method} = req;
|
|
@@ -30,14 +29,15 @@ module.exports = currify(async ({menuName}, req, res, next) => {
|
|
|
30
29
|
req,
|
|
31
30
|
res,
|
|
32
31
|
menuName,
|
|
32
|
+
readFile,
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
next();
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
async function onGET({req, res, menuName}) {
|
|
38
|
+
async function onGET({req, res, menuName, readFile}) {
|
|
39
39
|
const {dir} = req.query;
|
|
40
|
-
const url = req.url.replace(
|
|
40
|
+
const url = req.url.replace(PREFIX, '');
|
|
41
41
|
|
|
42
42
|
if (url === '/default')
|
|
43
43
|
return sendDefaultMenu(res);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {dirname, join} from 'node:path';
|
|
2
|
+
import {fileURLToPath} from 'node:url';
|
|
3
|
+
import {test, stub} from 'supertape';
|
|
4
|
+
import serveOnce from 'serve-once';
|
|
5
|
+
import threadIt from 'thread-it';
|
|
6
|
+
import userMenu from './user-menu.mjs';
|
|
7
|
+
import {readFileSync} from 'node:fs';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
|
|
12
|
+
const {request} = serveOnce(userMenu);
|
|
13
|
+
const userMenuPath = join(__dirname, '..', '.cloudcmd.menu.js');
|
|
14
|
+
const userMenuFile = readFileSync(userMenuPath, 'utf8');
|
|
15
|
+
|
|
16
|
+
const fixtureDir = new URL('fixture-user-menu', import.meta.url).pathname;
|
|
17
|
+
const fixtureMoveName = join(fixtureDir, 'io-mv.js');
|
|
18
|
+
const fixtureMoveFixName = join(fixtureDir, 'io-mv-fix.js');
|
|
19
|
+
const fixtureCopyName = join(fixtureDir, 'io-cp.js');
|
|
20
|
+
const fixtureCopyFixName = join(fixtureDir, 'io-cp-fix.js');
|
|
21
|
+
|
|
22
|
+
const fixtureMove = readFileSync(fixtureMoveName, 'utf8');
|
|
23
|
+
const fixtureMoveFix = readFileSync(fixtureMoveFixName, 'utf8');
|
|
24
|
+
const fixtureCopy = readFileSync(fixtureCopyName, 'utf8');
|
|
25
|
+
const fixtureCopyFix = readFileSync(fixtureCopyFixName, 'utf8');
|
|
26
|
+
|
|
27
|
+
test('cloudcmd: user menu', async (t) => {
|
|
28
|
+
const options = {
|
|
29
|
+
menuName: '.cloudcmd.menu.js',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const {body} = await request.get(`/api/v1/user-menu?dir=${__dirname}`, {
|
|
33
|
+
options,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
t.equal(userMenuFile, body);
|
|
37
|
+
t.end();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('cloudcmd: user menu: io.mv', async (t) => {
|
|
41
|
+
const readFile = stub().returns(fixtureMove);
|
|
42
|
+
const options = {
|
|
43
|
+
menuName: '.cloudcmd.menu.js',
|
|
44
|
+
readFile,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const {request} = serveOnce(userMenu);
|
|
48
|
+
|
|
49
|
+
const {body} = await request.get(`/api/v1/user-menu?dir=${__dirname}`, {
|
|
50
|
+
options,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
t.equal(body, fixtureMoveFix);
|
|
54
|
+
t.end();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('cloudcmd: user menu: io.cp', async (t) => {
|
|
58
|
+
const readFile = stub().returns(fixtureCopy);
|
|
59
|
+
const options = {
|
|
60
|
+
menuName: '.cloudcmd.menu.js',
|
|
61
|
+
readFile,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const {request} = serveOnce(userMenu);
|
|
65
|
+
|
|
66
|
+
const {body} = await request.get(`/api/v1/user-menu?dir=${__dirname}`, {
|
|
67
|
+
options,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
threadIt.terminate();
|
|
71
|
+
|
|
72
|
+
t.equal(body, fixtureCopyFix);
|
|
73
|
+
t.end();
|
|
74
|
+
});
|