codify-plugin-lib 1.0.182-beta7 → 1.0.182-beta9
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/dist/pty/background-pty.d.ts +1 -0
- package/dist/pty/background-pty.js +14 -11
- package/dist/pty/index.d.ts +1 -0
- package/dist/pty/seqeuntial-pty.d.ts +1 -0
- package/dist/pty/seqeuntial-pty.js +38 -9
- package/dist/test.d.ts +1 -0
- package/dist/test.js +5 -0
- package/package.json +2 -2
- package/src/pty/background-pty.ts +14 -12
- package/src/pty/index.ts +1 -0
- package/src/pty/seqeuntial-pty.ts +54 -12
- package/src/pty/sequential-pty.test.ts +124 -6
- package/src/test.ts +6 -0
- package/src/utils/file-utils.test.ts +9 -0
|
@@ -6,6 +6,7 @@ import { IPty, SpawnOptions, SpawnResult } from './index.js';
|
|
|
6
6
|
* without a tty (or even a stdin) attached so interactive commands will not work.
|
|
7
7
|
*/
|
|
8
8
|
export declare class BackgroundPty implements IPty {
|
|
9
|
+
private historyIgnore;
|
|
9
10
|
private basePty;
|
|
10
11
|
private promiseQueue;
|
|
11
12
|
constructor();
|
|
@@ -17,8 +17,10 @@ EventEmitter.defaultMaxListeners = 1000;
|
|
|
17
17
|
* without a tty (or even a stdin) attached so interactive commands will not work.
|
|
18
18
|
*/
|
|
19
19
|
export class BackgroundPty {
|
|
20
|
+
historyIgnore = Utils.getShell() === Shell.ZSH ? { HISTORY_IGNORE: '*' } : { HISTIGNORE: '*' };
|
|
20
21
|
basePty = pty.spawn(this.getDefaultShell(), ['-i'], {
|
|
21
|
-
env: process.env,
|
|
22
|
+
env: { ...process.env, ...this.historyIgnore },
|
|
23
|
+
name: nanoid(6),
|
|
22
24
|
handleFlowControl: true
|
|
23
25
|
});
|
|
24
26
|
promiseQueue = new PromiseQueue();
|
|
@@ -105,16 +107,17 @@ export class BackgroundPty {
|
|
|
105
107
|
let outputBuffer = '';
|
|
106
108
|
return new Promise(resolve => {
|
|
107
109
|
// zsh-specific commands
|
|
108
|
-
switch (Utils.getShell()) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
110
|
+
// switch (Utils.getShell()) {
|
|
111
|
+
// case Shell.ZSH: {
|
|
112
|
+
// this.basePty.write('setopt HIST_NO_STORE;\n');
|
|
113
|
+
// break;
|
|
114
|
+
// }
|
|
115
|
+
//
|
|
116
|
+
// default: {
|
|
117
|
+
// this.basePty.write('export HISTIGNORE=\'history*\';\n');
|
|
118
|
+
// break;
|
|
119
|
+
// }
|
|
120
|
+
// }
|
|
118
121
|
this.basePty.write(' unset PS1;\n');
|
|
119
122
|
this.basePty.write(' unset PS0;\n');
|
|
120
123
|
this.basePty.write(' echo setup complete\\"\n');
|
package/dist/pty/index.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import pty from '@homebridge/node-pty-prebuilt-multiarch';
|
|
2
|
+
import { Ajv } from 'ajv';
|
|
3
|
+
import { CommandRequestResponseDataSchema, MessageCmd } from 'codify-schemas';
|
|
4
|
+
import { nanoid } from 'nanoid';
|
|
2
5
|
import { EventEmitter } from 'node:events';
|
|
3
6
|
import stripAnsi from 'strip-ansi';
|
|
4
7
|
import { Shell, Utils } from '../utils/index.js';
|
|
5
8
|
import { VerbosityLevel } from '../utils/verbosity-level.js';
|
|
6
9
|
import { SpawnError, SpawnStatus } from './index.js';
|
|
7
10
|
EventEmitter.defaultMaxListeners = 1000;
|
|
11
|
+
const ajv = new Ajv({
|
|
12
|
+
strict: true,
|
|
13
|
+
});
|
|
14
|
+
const validateSudoRequestResponse = ajv.compile(CommandRequestResponseDataSchema);
|
|
8
15
|
/**
|
|
9
16
|
* The background pty is a specialized pty designed for speed. It can launch multiple tasks
|
|
10
17
|
* in parallel by moving them to the background. It attaches unix FIFO pipes to each process
|
|
@@ -20,6 +27,10 @@ export class SequentialPty {
|
|
|
20
27
|
return spawnResult;
|
|
21
28
|
}
|
|
22
29
|
async spawnSafe(cmd, options) {
|
|
30
|
+
// If sudo is required, we must delegate to the main codify process.
|
|
31
|
+
if (options?.interactive || options?.requiresRoot) {
|
|
32
|
+
return this.externalSpawn(cmd, options);
|
|
33
|
+
}
|
|
23
34
|
console.log(`Running command: ${cmd}` + (options?.cwd ? `(${options?.cwd})` : ''));
|
|
24
35
|
return new Promise((resolve) => {
|
|
25
36
|
const output = [];
|
|
@@ -30,14 +41,14 @@ export class SequentialPty {
|
|
|
30
41
|
...process.env, ...options?.env,
|
|
31
42
|
TERM_PROGRAM: 'codify',
|
|
32
43
|
COMMAND_MODE: 'unix2003',
|
|
33
|
-
COLORTERM: 'truecolor',
|
|
44
|
+
COLORTERM: 'truecolor',
|
|
45
|
+
...historyIgnore
|
|
34
46
|
};
|
|
35
47
|
// Initial terminal dimensions
|
|
36
48
|
const initialCols = process.stdout.columns ?? 80;
|
|
37
49
|
const initialRows = process.stdout.rows ?? 24;
|
|
38
|
-
const args = (options?.interactive ?? false) ? ['-i', '-c', cmd] : ['-c', cmd];
|
|
39
50
|
// Run the command in a pty for interactivity
|
|
40
|
-
const mPty = pty.spawn(this.getDefaultShell(),
|
|
51
|
+
const mPty = pty.spawn(this.getDefaultShell(), ['-c', cmd], {
|
|
41
52
|
...options,
|
|
42
53
|
cols: initialCols,
|
|
43
54
|
rows: initialRows,
|
|
@@ -49,20 +60,14 @@ export class SequentialPty {
|
|
|
49
60
|
}
|
|
50
61
|
output.push(data.toString());
|
|
51
62
|
});
|
|
52
|
-
const stdinListener = (data) => {
|
|
53
|
-
mPty.write(data.toString());
|
|
54
|
-
};
|
|
55
63
|
const resizeListener = () => {
|
|
56
64
|
const { columns, rows } = process.stdout;
|
|
57
65
|
mPty.resize(columns, rows);
|
|
58
66
|
};
|
|
59
67
|
// Listen to resize events for the terminal window;
|
|
60
68
|
process.stdout.on('resize', resizeListener);
|
|
61
|
-
// Listen for user input
|
|
62
|
-
process.stdin.on('data', stdinListener);
|
|
63
69
|
mPty.onExit((result) => {
|
|
64
70
|
process.stdout.off('resize', resizeListener);
|
|
65
|
-
process.stdin.off('data', stdinListener);
|
|
66
71
|
resolve({
|
|
67
72
|
status: result.exitCode === 0 ? SpawnStatus.SUCCESS : SpawnStatus.ERROR,
|
|
68
73
|
exitCode: result.exitCode,
|
|
@@ -78,6 +83,30 @@ export class SequentialPty {
|
|
|
78
83
|
signal: 0,
|
|
79
84
|
};
|
|
80
85
|
}
|
|
86
|
+
// For safety reasons, requests that require sudo or are interactive must be run via the main client
|
|
87
|
+
async externalSpawn(cmd, opts) {
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
const requestId = nanoid(8);
|
|
90
|
+
const listener = (data) => {
|
|
91
|
+
if (data.requestId === requestId) {
|
|
92
|
+
process.removeListener('message', listener);
|
|
93
|
+
if (!validateSudoRequestResponse(data.data)) {
|
|
94
|
+
throw new Error(`Invalid response for sudo request: ${JSON.stringify(validateSudoRequestResponse.errors, null, 2)}`);
|
|
95
|
+
}
|
|
96
|
+
resolve(data.data);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
process.on('message', listener);
|
|
100
|
+
process.send({
|
|
101
|
+
cmd: MessageCmd.COMMAND_REQUEST,
|
|
102
|
+
data: {
|
|
103
|
+
command: cmd,
|
|
104
|
+
options: opts ?? {},
|
|
105
|
+
},
|
|
106
|
+
requestId
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
81
110
|
getDefaultShell() {
|
|
82
111
|
return process.env.SHELL;
|
|
83
112
|
}
|
package/dist/test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/test.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codify-plugin-lib",
|
|
3
|
-
"version": "1.0.182-
|
|
3
|
+
"version": "1.0.182-beta9",
|
|
4
4
|
"description": "Library plugin library",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"ajv": "^8.12.0",
|
|
23
23
|
"ajv-formats": "^2.1.1",
|
|
24
24
|
"clean-deep": "^3.4.0",
|
|
25
|
-
"codify-schemas": "1.0.86",
|
|
25
|
+
"codify-schemas": "1.0.86-beta4",
|
|
26
26
|
"lodash.isequal": "^4.5.0",
|
|
27
27
|
"nanoid": "^5.0.9",
|
|
28
28
|
"strip-ansi": "^7.1.0",
|
|
@@ -20,8 +20,10 @@ EventEmitter.defaultMaxListeners = 1000;
|
|
|
20
20
|
* without a tty (or even a stdin) attached so interactive commands will not work.
|
|
21
21
|
*/
|
|
22
22
|
export class BackgroundPty implements IPty {
|
|
23
|
+
private historyIgnore = Utils.getShell() === Shell.ZSH ? { HISTORY_IGNORE: '*' } : { HISTIGNORE: '*' };
|
|
23
24
|
private basePty = pty.spawn(this.getDefaultShell(), ['-i'], {
|
|
24
|
-
env: process.env,
|
|
25
|
+
env: { ...process.env, ...this.historyIgnore },
|
|
26
|
+
name: nanoid(6),
|
|
25
27
|
handleFlowControl: true
|
|
26
28
|
});
|
|
27
29
|
|
|
@@ -129,17 +131,17 @@ export class BackgroundPty implements IPty {
|
|
|
129
131
|
|
|
130
132
|
return new Promise(resolve => {
|
|
131
133
|
// zsh-specific commands
|
|
132
|
-
switch (Utils.getShell()) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
134
|
+
// switch (Utils.getShell()) {
|
|
135
|
+
// case Shell.ZSH: {
|
|
136
|
+
// this.basePty.write('setopt HIST_NO_STORE;\n');
|
|
137
|
+
// break;
|
|
138
|
+
// }
|
|
139
|
+
//
|
|
140
|
+
// default: {
|
|
141
|
+
// this.basePty.write('export HISTIGNORE=\'history*\';\n');
|
|
142
|
+
// break;
|
|
143
|
+
// }
|
|
144
|
+
// }
|
|
143
145
|
|
|
144
146
|
this.basePty.write(' unset PS1;\n');
|
|
145
147
|
this.basePty.write(' unset PS0;\n')
|
package/src/pty/index.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import pty from '@homebridge/node-pty-prebuilt-multiarch';
|
|
2
|
+
import { Ajv } from 'ajv';
|
|
3
|
+
import {
|
|
4
|
+
CommandRequestResponseData,
|
|
5
|
+
CommandRequestResponseDataSchema,
|
|
6
|
+
IpcMessageV2,
|
|
7
|
+
MessageCmd
|
|
8
|
+
} from 'codify-schemas';
|
|
9
|
+
import { nanoid } from 'nanoid';
|
|
2
10
|
import { EventEmitter } from 'node:events';
|
|
3
11
|
import stripAnsi from 'strip-ansi';
|
|
4
12
|
|
|
@@ -8,6 +16,11 @@ import { IPty, SpawnError, SpawnOptions, SpawnResult, SpawnStatus } from './inde
|
|
|
8
16
|
|
|
9
17
|
EventEmitter.defaultMaxListeners = 1000;
|
|
10
18
|
|
|
19
|
+
const ajv = new Ajv({
|
|
20
|
+
strict: true,
|
|
21
|
+
});
|
|
22
|
+
const validateSudoRequestResponse = ajv.compile(CommandRequestResponseDataSchema);
|
|
23
|
+
|
|
11
24
|
/**
|
|
12
25
|
* The background pty is a specialized pty designed for speed. It can launch multiple tasks
|
|
13
26
|
* in parallel by moving them to the background. It attaches unix FIFO pipes to each process
|
|
@@ -26,11 +39,15 @@ export class SequentialPty implements IPty {
|
|
|
26
39
|
}
|
|
27
40
|
|
|
28
41
|
async spawnSafe(cmd: string, options?: SpawnOptions): Promise<SpawnResult> {
|
|
42
|
+
// If sudo is required, we must delegate to the main codify process.
|
|
43
|
+
if (options?.interactive || options?.requiresRoot) {
|
|
44
|
+
return this.externalSpawn(cmd, options);
|
|
45
|
+
}
|
|
46
|
+
|
|
29
47
|
console.log(`Running command: ${cmd}` + (options?.cwd ? `(${options?.cwd})` : ''))
|
|
30
48
|
|
|
31
49
|
return new Promise((resolve) => {
|
|
32
50
|
const output: string[] = [];
|
|
33
|
-
|
|
34
51
|
const historyIgnore = Utils.getShell() === Shell.ZSH ? { HISTORY_IGNORE: '*' } : { HISTIGNORE: '*' };
|
|
35
52
|
|
|
36
53
|
// If TERM_PROGRAM=Apple_Terminal is set then ANSI escape characters may be included
|
|
@@ -39,17 +56,16 @@ export class SequentialPty implements IPty {
|
|
|
39
56
|
...process.env, ...options?.env,
|
|
40
57
|
TERM_PROGRAM: 'codify',
|
|
41
58
|
COMMAND_MODE: 'unix2003',
|
|
42
|
-
COLORTERM: 'truecolor',
|
|
59
|
+
COLORTERM: 'truecolor',
|
|
60
|
+
...historyIgnore
|
|
43
61
|
}
|
|
44
62
|
|
|
45
63
|
// Initial terminal dimensions
|
|
46
64
|
const initialCols = process.stdout.columns ?? 80;
|
|
47
65
|
const initialRows = process.stdout.rows ?? 24;
|
|
48
66
|
|
|
49
|
-
const args = (options?.interactive ?? false) ? ['-i', '-c', cmd] : ['-c', cmd]
|
|
50
|
-
|
|
51
67
|
// Run the command in a pty for interactivity
|
|
52
|
-
const mPty = pty.spawn(this.getDefaultShell(),
|
|
68
|
+
const mPty = pty.spawn(this.getDefaultShell(), ['-c', cmd], {
|
|
53
69
|
...options,
|
|
54
70
|
cols: initialCols,
|
|
55
71
|
rows: initialRows,
|
|
@@ -64,10 +80,6 @@ export class SequentialPty implements IPty {
|
|
|
64
80
|
output.push(data.toString());
|
|
65
81
|
})
|
|
66
82
|
|
|
67
|
-
const stdinListener = (data: any) => {
|
|
68
|
-
mPty.write(data.toString());
|
|
69
|
-
};
|
|
70
|
-
|
|
71
83
|
const resizeListener = () => {
|
|
72
84
|
const { columns, rows } = process.stdout;
|
|
73
85
|
mPty.resize(columns, rows);
|
|
@@ -75,12 +87,9 @@ export class SequentialPty implements IPty {
|
|
|
75
87
|
|
|
76
88
|
// Listen to resize events for the terminal window;
|
|
77
89
|
process.stdout.on('resize', resizeListener);
|
|
78
|
-
// Listen for user input
|
|
79
|
-
process.stdin.on('data', stdinListener);
|
|
80
90
|
|
|
81
91
|
mPty.onExit((result) => {
|
|
82
92
|
process.stdout.off('resize', resizeListener);
|
|
83
|
-
process.stdin.off('data', stdinListener);
|
|
84
93
|
|
|
85
94
|
resolve({
|
|
86
95
|
status: result.exitCode === 0 ? SpawnStatus.SUCCESS : SpawnStatus.ERROR,
|
|
@@ -99,6 +108,39 @@ export class SequentialPty implements IPty {
|
|
|
99
108
|
}
|
|
100
109
|
}
|
|
101
110
|
|
|
111
|
+
// For safety reasons, requests that require sudo or are interactive must be run via the main client
|
|
112
|
+
async externalSpawn(
|
|
113
|
+
cmd: string,
|
|
114
|
+
opts: SpawnOptions
|
|
115
|
+
): Promise<SpawnResult> {
|
|
116
|
+
return new Promise((resolve) => {
|
|
117
|
+
const requestId = nanoid(8);
|
|
118
|
+
|
|
119
|
+
const listener = (data: IpcMessageV2)=> {
|
|
120
|
+
if (data.requestId === requestId) {
|
|
121
|
+
process.removeListener('message', listener);
|
|
122
|
+
|
|
123
|
+
if (!validateSudoRequestResponse(data.data)) {
|
|
124
|
+
throw new Error(`Invalid response for sudo request: ${JSON.stringify(validateSudoRequestResponse.errors, null, 2)}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
resolve(data.data as unknown as CommandRequestResponseData);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
process.on('message', listener);
|
|
132
|
+
|
|
133
|
+
process.send!(<IpcMessageV2>{
|
|
134
|
+
cmd: MessageCmd.COMMAND_REQUEST,
|
|
135
|
+
data: {
|
|
136
|
+
command: cmd,
|
|
137
|
+
options: opts ?? {},
|
|
138
|
+
},
|
|
139
|
+
requestId
|
|
140
|
+
})
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
102
144
|
private getDefaultShell(): string {
|
|
103
145
|
return process.env.SHELL!;
|
|
104
146
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { SequentialPty } from './seqeuntial-pty.js';
|
|
3
3
|
import { VerbosityLevel } from '../utils/verbosity-level.js';
|
|
4
|
+
import { MessageStatus, SpawnStatus } from 'codify-schemas/src/types/index.js';
|
|
5
|
+
import { IpcMessageV2, MessageCmd } from 'codify-schemas';
|
|
4
6
|
|
|
5
7
|
describe('SequentialPty tests', () => {
|
|
6
8
|
it('Can launch a simple command', async () => {
|
|
@@ -33,8 +35,8 @@ describe('SequentialPty tests', () => {
|
|
|
33
35
|
const resultFailed = await pty.spawnSafe('which sjkdhsakjdhjkash');
|
|
34
36
|
expect(resultFailed).toMatchObject({
|
|
35
37
|
status: 'error',
|
|
36
|
-
exitCode:
|
|
37
|
-
data: '
|
|
38
|
+
exitCode: 1,
|
|
39
|
+
data: 'sjkdhsakjdhjkash not found' // This might change on different os or shells. Keep for now.
|
|
38
40
|
})
|
|
39
41
|
});
|
|
40
42
|
|
|
@@ -50,12 +52,128 @@ describe('SequentialPty tests', () => {
|
|
|
50
52
|
});
|
|
51
53
|
|
|
52
54
|
it('It can launch a command in interactive mode', async () => {
|
|
53
|
-
const
|
|
55
|
+
const originalSend = process.send;
|
|
56
|
+
process.send = (req: IpcMessageV2) => {
|
|
57
|
+
expect(req).toMatchObject({
|
|
58
|
+
cmd: MessageCmd.COMMAND_REQUEST,
|
|
59
|
+
requestId: expect.any(String),
|
|
60
|
+
data: {
|
|
61
|
+
command: 'ls',
|
|
62
|
+
options: {
|
|
63
|
+
cwd: '/tmp',
|
|
64
|
+
interactive: true,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// This may look confusing but what we're doing here is directly finding the process listener and calling it without going through serialization
|
|
70
|
+
const listeners = process.listeners('message');
|
|
71
|
+
listeners[2](({
|
|
72
|
+
cmd: MessageCmd.COMMAND_REQUEST,
|
|
73
|
+
requestId: req.requestId,
|
|
74
|
+
status: MessageStatus.SUCCESS,
|
|
75
|
+
data: {
|
|
76
|
+
status: SpawnStatus.SUCCESS,
|
|
77
|
+
exitCode: 0,
|
|
78
|
+
data: 'My data',
|
|
79
|
+
}
|
|
80
|
+
}))
|
|
81
|
+
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const $ = new SequentialPty();
|
|
86
|
+
const resultSuccess = await $.spawnSafe('ls', { interactive: true, cwd: '/tmp' });
|
|
54
87
|
|
|
55
|
-
const resultSuccess = await pty.spawnSafe('ls', { interactive: true });
|
|
56
88
|
expect(resultSuccess).toMatchObject({
|
|
57
89
|
status: 'success',
|
|
58
90
|
exitCode: 0,
|
|
59
|
-
})
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
process.send = originalSend;
|
|
60
94
|
});
|
|
95
|
+
|
|
96
|
+
it('It can work with root (sudo)', async () => {
|
|
97
|
+
const originalSend = process.send;
|
|
98
|
+
process.send = (req: IpcMessageV2) => {
|
|
99
|
+
expect(req).toMatchObject({
|
|
100
|
+
cmd: MessageCmd.COMMAND_REQUEST,
|
|
101
|
+
requestId: expect.any(String),
|
|
102
|
+
data: {
|
|
103
|
+
command: 'ls',
|
|
104
|
+
options: {
|
|
105
|
+
interactive: true,
|
|
106
|
+
requiresRoot: true,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// This may look confusing but what we're doing here is directly finding the process listener and calling it without going through serialization
|
|
112
|
+
const listeners = process.listeners('message');
|
|
113
|
+
listeners[2](({
|
|
114
|
+
cmd: MessageCmd.COMMAND_REQUEST,
|
|
115
|
+
requestId: req.requestId,
|
|
116
|
+
status: MessageStatus.SUCCESS,
|
|
117
|
+
data: {
|
|
118
|
+
status: SpawnStatus.SUCCESS,
|
|
119
|
+
exitCode: 0,
|
|
120
|
+
data: 'My data',
|
|
121
|
+
}
|
|
122
|
+
}))
|
|
123
|
+
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const $ = new SequentialPty();
|
|
128
|
+
const resultSuccess = await $.spawn('ls', { interactive: true, requiresRoot: true });
|
|
129
|
+
|
|
130
|
+
expect(resultSuccess).toMatchObject({
|
|
131
|
+
status: 'success',
|
|
132
|
+
exitCode: 0,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
process.send = originalSend;
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('It can handle errors when in sudo', async () => {
|
|
139
|
+
const originalSend = process.send;
|
|
140
|
+
process.send = (req: IpcMessageV2) => {
|
|
141
|
+
expect(req).toMatchObject({
|
|
142
|
+
cmd: MessageCmd.COMMAND_REQUEST,
|
|
143
|
+
requestId: expect.any(String),
|
|
144
|
+
data: {
|
|
145
|
+
command: 'ls',
|
|
146
|
+
options: {
|
|
147
|
+
requiresRoot: true,
|
|
148
|
+
interactive: true,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// This may look confusing but what we're doing here is directly finding the process listener and calling it without going through serialization
|
|
154
|
+
const listeners = process.listeners('message');
|
|
155
|
+
listeners[2](({
|
|
156
|
+
cmd: MessageCmd.COMMAND_REQUEST,
|
|
157
|
+
requestId: req.requestId,
|
|
158
|
+
status: MessageStatus.SUCCESS,
|
|
159
|
+
data: {
|
|
160
|
+
status: SpawnStatus.ERROR,
|
|
161
|
+
exitCode: 127,
|
|
162
|
+
data: 'My data',
|
|
163
|
+
}
|
|
164
|
+
}))
|
|
165
|
+
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const $ = new SequentialPty();
|
|
170
|
+
const resultSuccess = await $.spawnSafe('ls', { interactive: true, requiresRoot: true });
|
|
171
|
+
|
|
172
|
+
expect(resultSuccess).toMatchObject({
|
|
173
|
+
status: SpawnStatus.ERROR,
|
|
174
|
+
exitCode: 127,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
process.send = originalSend;
|
|
178
|
+
})
|
|
61
179
|
})
|
package/src/test.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { describe, it } from 'vitest';
|
|
2
|
+
import { FileUtils } from './file-utils.js';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
describe('File utils tests', { timeout: 100_000_000 }, () => {
|
|
6
|
+
it('Can download a file', async () => {
|
|
7
|
+
// await FileUtils.downloadFile('https://download.jetbrains.com/webstorm/WebStorm-2025.3.1-aarch64.dmg?_gl=1*1huoi7o*_gcl_aw*R0NMLjE3NjU3NDAwMTcuQ2p3S0NBaUEzZm5KQmhBZ0Vpd0F5cW1ZNVhLVENlbHJOcTk2YXdjZVlfMS1wdE91MXc0WDk2bFJkVDM3QURhUFNJMUtwNVVSVUhxWTJob0NuZ0FRQXZEX0J3RQ..*_gcl_au*MjA0MDQ0MjE2My4xNzYzNjQzNzMz*FPAU*MjA0MDQ0MjE2My4xNzYzNjQzNzMz*_ga*MTYxMDg4MTkzMi4xNzYzNjQzNzMz*_ga_9J976DJZ68*czE3NjYzNjI5ODAkbzEyJGcxJHQxNzY2MzYzMDQwJGo2MCRsMCRoMA..', path.join(process.cwd(), 'google.html'));
|
|
8
|
+
})
|
|
9
|
+
})
|