codify-plugin-test 0.0.52 → 0.0.53-beta1
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/plugin-process.js +13 -40
- package/dist/plugin-process.js.map +1 -1
- package/dist/shell.d.ts +12 -0
- package/dist/shell.js +37 -0
- package/dist/shell.js.map +1 -0
- package/dist/spawn.d.ts +14 -0
- package/dist/spawn.js +58 -0
- package/dist/spawn.js.map +1 -0
- package/package.json +6 -4
- package/src/plugin-process.ts +23 -73
- package/src/shell.ts +45 -0
- package/src/spawn.ts +91 -0
package/dist/plugin-process.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import Ajv from 'ajv';
|
|
2
|
-
import { IpcMessageSchema, MessageCmd
|
|
2
|
+
import { CommandRequestDataSchema, IpcMessageSchema, MessageCmd } from 'codify-schemas';
|
|
3
3
|
import { nanoid } from 'nanoid';
|
|
4
|
-
import { fork
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import { CodifyTestUtils } from './test-utils.js';
|
|
4
|
+
import { fork } from 'node:child_process';
|
|
7
5
|
import fs from 'node:fs/promises';
|
|
8
6
|
import * as os from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { spawnSafe } from './spawn.js';
|
|
9
|
+
import { CodifyTestUtils } from './test-utils.js';
|
|
9
10
|
const ajv = new Ajv.default({
|
|
10
11
|
strict: true
|
|
11
12
|
});
|
|
12
13
|
const ipcMessageValidator = ajv.compile(IpcMessageSchema);
|
|
13
|
-
const
|
|
14
|
+
const commandRequestValidator = ajv.compile(CommandRequestDataSchema);
|
|
14
15
|
export class PluginProcess {
|
|
15
16
|
childProcess;
|
|
16
17
|
constructor(pluginPath) {
|
|
@@ -70,23 +71,23 @@ export class PluginProcess {
|
|
|
70
71
|
if (!ipcMessageValidator(message)) {
|
|
71
72
|
throw new Error(`Invalid message from plugin. ${JSON.stringify(message, null, 2)}`);
|
|
72
73
|
}
|
|
73
|
-
if (message.cmd === MessageCmd.
|
|
74
|
+
if (message.cmd === MessageCmd.COMMAND_REQUEST) {
|
|
74
75
|
const { data, requestId } = message;
|
|
75
|
-
if (!
|
|
76
|
-
throw new Error(`Invalid sudo request from plugin. ${JSON.stringify(
|
|
76
|
+
if (!commandRequestValidator(data)) {
|
|
77
|
+
throw new Error(`Invalid sudo request from plugin. ${JSON.stringify(commandRequestValidator.errors, null, 2)}`);
|
|
77
78
|
}
|
|
78
79
|
const { command, options } = data;
|
|
79
|
-
const result = await
|
|
80
|
+
const result = await spawnSafe(command, options);
|
|
80
81
|
process.send({
|
|
81
|
-
cmd: MessageCmd.
|
|
82
|
+
cmd: MessageCmd.COMMAND_REQUEST + '_Response',
|
|
82
83
|
data: result,
|
|
83
84
|
requestId,
|
|
84
85
|
});
|
|
85
86
|
}
|
|
86
87
|
if (message.cmd === MessageCmd.PRESS_KEY_TO_CONTINUE_REQUEST) {
|
|
87
88
|
const { data, requestId } = message;
|
|
88
|
-
if (!
|
|
89
|
-
throw new Error(`Invalid sudo request from plugin. ${JSON.stringify(
|
|
89
|
+
if (!commandRequestValidator(data)) {
|
|
90
|
+
throw new Error(`Invalid sudo request from plugin. ${JSON.stringify(commandRequestValidator.errors, null, 2)}`);
|
|
90
91
|
}
|
|
91
92
|
process.send({
|
|
92
93
|
cmd: MessageCmd.PRESS_KEY_TO_CONTINUE_REQUEST + '_Response',
|
|
@@ -113,32 +114,4 @@ export class PluginProcess {
|
|
|
113
114
|
});
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
|
-
async function sudoSpawn(cmd, opts) {
|
|
117
|
-
return new Promise((resolve) => {
|
|
118
|
-
const output = [];
|
|
119
|
-
const _cmd = `sudo ${cmd}`;
|
|
120
|
-
const _process = spawn(`source ~/.zshrc; ${_cmd}`, [], {
|
|
121
|
-
...opts,
|
|
122
|
-
shell: 'zsh',
|
|
123
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
124
|
-
});
|
|
125
|
-
const { stderr, stdout } = _process;
|
|
126
|
-
stdout.setEncoding('utf8');
|
|
127
|
-
stderr.setEncoding('utf8');
|
|
128
|
-
stdout.on('data', (data) => {
|
|
129
|
-
output.push(data.toString());
|
|
130
|
-
});
|
|
131
|
-
stderr.on('data', (data) => {
|
|
132
|
-
output.push(data.toString());
|
|
133
|
-
});
|
|
134
|
-
stdout.pipe(process.stdout);
|
|
135
|
-
stderr.pipe(process.stderr);
|
|
136
|
-
_process.on('close', (code) => {
|
|
137
|
-
resolve({
|
|
138
|
-
data: output.join(''),
|
|
139
|
-
status: code === 0 ? SpawnStatus.SUCCESS : SpawnStatus.ERROR,
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
117
|
//# sourceMappingURL=plugin-process.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-process.js","sourceRoot":"","sources":["../src/plugin-process.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAGL,gBAAgB,EAEhB,UAAU,
|
|
1
|
+
{"version":3,"file":"plugin-process.js","sourceRoot":"","sources":["../src/plugin-process.ts"],"names":[],"mappings":"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"}
|
package/dist/shell.d.ts
ADDED
package/dist/shell.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export var Shell;
|
|
2
|
+
(function (Shell) {
|
|
3
|
+
Shell["ZSH"] = "zsh";
|
|
4
|
+
Shell["BASH"] = "bash";
|
|
5
|
+
Shell["SH"] = "sh";
|
|
6
|
+
Shell["KSH"] = "ksh";
|
|
7
|
+
Shell["CSH"] = "csh";
|
|
8
|
+
Shell["FISH"] = "fish";
|
|
9
|
+
})(Shell || (Shell = {}));
|
|
10
|
+
export const ShellUtils = {
|
|
11
|
+
getShell() {
|
|
12
|
+
const shell = process.env.SHELL || '';
|
|
13
|
+
if (shell.endsWith('bash')) {
|
|
14
|
+
return Shell.BASH;
|
|
15
|
+
}
|
|
16
|
+
if (shell.endsWith('zsh')) {
|
|
17
|
+
return Shell.ZSH;
|
|
18
|
+
}
|
|
19
|
+
if (shell.endsWith('sh')) {
|
|
20
|
+
return Shell.SH;
|
|
21
|
+
}
|
|
22
|
+
if (shell.endsWith('csh')) {
|
|
23
|
+
return Shell.CSH;
|
|
24
|
+
}
|
|
25
|
+
if (shell.endsWith('ksh')) {
|
|
26
|
+
return Shell.KSH;
|
|
27
|
+
}
|
|
28
|
+
if (shell.endsWith('fish')) {
|
|
29
|
+
return Shell.FISH;
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
},
|
|
33
|
+
getDefaultShell() {
|
|
34
|
+
return process.env.SHELL;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=shell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../src/shell.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,KAOX;AAPD,WAAY,KAAK;IACf,oBAAW,CAAA;IACX,sBAAa,CAAA;IACb,kBAAS,CAAA;IACT,oBAAW,CAAA;IACX,oBAAW,CAAA;IACX,sBAAa,CAAA;AACf,CAAC,EAPW,KAAK,KAAL,KAAK,QAOhB;AAGD,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,QAAQ;QACN,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;QAEtC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,OAAO,KAAK,CAAC,IAAI,CAAA;QACnB,CAAC;QAED,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,KAAK,CAAC,GAAG,CAAA;QAClB,CAAC;QAED,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,OAAO,KAAK,CAAC,EAAE,CAAA;QACjB,CAAC;QAED,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,KAAK,CAAC,GAAG,CAAA;QAClB,CAAC;QAED,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,KAAK,CAAC,GAAG,CAAA;QAClB,CAAC;QAED,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,OAAO,KAAK,CAAC,IAAI,CAAA;QACnB,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,eAAe;QACb,OAAO,OAAO,CAAC,GAAG,CAAC,KAAM,CAAC;IAC5B,CAAC;CACF,CAAA"}
|
package/dist/spawn.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SpawnStatus } from 'codify-schemas';
|
|
2
|
+
export interface SpawnResult {
|
|
3
|
+
status: SpawnStatus;
|
|
4
|
+
exitCode: number;
|
|
5
|
+
data: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SpawnOptions {
|
|
8
|
+
cwd?: string;
|
|
9
|
+
env?: Record<string, unknown>;
|
|
10
|
+
interactive?: boolean;
|
|
11
|
+
requiresRoot?: boolean;
|
|
12
|
+
stdin?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function spawnSafe(cmd: string, options?: SpawnOptions): Promise<SpawnResult>;
|
package/dist/spawn.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as pty from '@homebridge/node-pty-prebuilt-multiarch';
|
|
2
|
+
import { SpawnStatus } from 'codify-schemas';
|
|
3
|
+
import stripAnsi from 'strip-ansi';
|
|
4
|
+
import { Shell, ShellUtils } from './shell.js';
|
|
5
|
+
export function spawnSafe(cmd, options) {
|
|
6
|
+
if (cmd.toLowerCase().includes('sudo')) {
|
|
7
|
+
throw new Error('Command must not include sudo');
|
|
8
|
+
}
|
|
9
|
+
process.stdout.write(`Running command: ${options?.requiresRoot ? 'sudo' : ''} ${cmd}` + (options?.cwd ? `(${options?.cwd})` : ''));
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
const output = [];
|
|
12
|
+
const historyIgnore = ShellUtils.getShell() === Shell.ZSH ? { HISTORY_IGNORE: '*' } : { HISTIGNORE: '*' };
|
|
13
|
+
const env = {
|
|
14
|
+
...process.env, ...options?.env,
|
|
15
|
+
TERM_PROGRAM: 'codify',
|
|
16
|
+
COMMAND_MODE: 'unix2003',
|
|
17
|
+
COLORTERM: 'truecolor',
|
|
18
|
+
...historyIgnore
|
|
19
|
+
};
|
|
20
|
+
const initialCols = process.stdout.columns ?? 80;
|
|
21
|
+
const initialRows = process.stdout.rows ?? 24;
|
|
22
|
+
const command = options?.requiresRoot ? `sudo ${cmd}` : cmd;
|
|
23
|
+
const args = options?.interactive ? ['-i', '-c', command] : ['-c', command];
|
|
24
|
+
const mPty = pty.spawn(ShellUtils.getDefaultShell(), args, {
|
|
25
|
+
...options,
|
|
26
|
+
cols: initialCols,
|
|
27
|
+
rows: initialRows,
|
|
28
|
+
env
|
|
29
|
+
});
|
|
30
|
+
mPty.onData((data) => {
|
|
31
|
+
process.stdout.write(data);
|
|
32
|
+
output.push(data.toString());
|
|
33
|
+
});
|
|
34
|
+
const resizeListener = () => {
|
|
35
|
+
const { columns, rows } = process.stdout;
|
|
36
|
+
mPty.resize(columns, rows);
|
|
37
|
+
};
|
|
38
|
+
const stdinListener = (data) => {
|
|
39
|
+
mPty.write(data.toString());
|
|
40
|
+
};
|
|
41
|
+
process.stdout.on('resize', resizeListener);
|
|
42
|
+
if (options?.stdin) {
|
|
43
|
+
process.stdin.on('data', stdinListener);
|
|
44
|
+
}
|
|
45
|
+
mPty.onExit((result) => {
|
|
46
|
+
process.stdout.off('resize', resizeListener);
|
|
47
|
+
if (options?.stdin) {
|
|
48
|
+
process.stdin.off('data', stdinListener);
|
|
49
|
+
}
|
|
50
|
+
resolve({
|
|
51
|
+
status: result.exitCode === 0 ? SpawnStatus.SUCCESS : SpawnStatus.ERROR,
|
|
52
|
+
exitCode: result.exitCode,
|
|
53
|
+
data: stripAnsi(output.join('\n').trim()),
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=spawn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn.js","sourceRoot":"","sources":["../src/spawn.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codify-plugin-test",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.53-beta1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
@@ -16,12 +16,14 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"ajv": "^8.12.0",
|
|
18
18
|
"ajv-formats": "^3.0.1",
|
|
19
|
-
"codify-schemas": "1.0.
|
|
19
|
+
"codify-schemas": "1.0.86-beta5",
|
|
20
20
|
"lodash.matches": "^4.6.0",
|
|
21
21
|
"lodash.differencewith": "4.5.0",
|
|
22
22
|
"lodash.unionby": "^4.8.0",
|
|
23
23
|
"nanoid": "^5.0.9",
|
|
24
|
-
"chalk": "^5.4.1"
|
|
24
|
+
"chalk": "^5.4.1",
|
|
25
|
+
"@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
|
|
26
|
+
"strip-ansi": "^7.1.2"
|
|
25
27
|
},
|
|
26
28
|
"devDependencies": {
|
|
27
29
|
"@oclif/prettier-config": "^0.2.1",
|
|
@@ -37,7 +39,7 @@
|
|
|
37
39
|
"tsx": "^4.7.3",
|
|
38
40
|
"typescript": "^5",
|
|
39
41
|
"vitest": "^1.4.0",
|
|
40
|
-
"codify-plugin-lib": "1.0.
|
|
42
|
+
"codify-plugin-lib": "1.0.182-beta9"
|
|
41
43
|
},
|
|
42
44
|
"engines": {
|
|
43
45
|
"node": ">=18.0.0"
|
package/src/plugin-process.ts
CHANGED
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
import Ajv from 'ajv';
|
|
2
2
|
import {
|
|
3
|
-
ApplyRequestData,
|
|
3
|
+
ApplyRequestData,
|
|
4
|
+
CommandRequestData,
|
|
5
|
+
CommandRequestDataSchema,
|
|
6
|
+
ImportRequestData,
|
|
7
|
+
ImportResponseData,
|
|
4
8
|
InitializeResponseData,
|
|
5
9
|
IpcMessageSchema,
|
|
6
10
|
IpcMessageV2,
|
|
7
|
-
MessageCmd,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
MessageCmd,
|
|
12
|
+
PlanRequestData,
|
|
13
|
+
PlanResponseData,
|
|
14
|
+
ValidateRequestData,
|
|
15
|
+
ValidateResponseData
|
|
11
16
|
} from 'codify-schemas';
|
|
12
17
|
import { nanoid } from 'nanoid';
|
|
13
|
-
import { ChildProcess,
|
|
18
|
+
import { ChildProcess, fork } from 'node:child_process';
|
|
19
|
+
import fs from 'node:fs/promises';
|
|
20
|
+
import * as os from 'node:os';
|
|
14
21
|
import path from 'node:path';
|
|
15
22
|
|
|
23
|
+
import { spawnSafe } from './spawn.js';
|
|
16
24
|
import { CodifyTestUtils } from './test-utils.js';
|
|
17
|
-
import fs from 'node:fs/promises';
|
|
18
|
-
import * as os from 'node:os';
|
|
19
25
|
|
|
20
26
|
const ajv = new Ajv.default({
|
|
21
27
|
strict: true
|
|
22
28
|
});
|
|
23
29
|
|
|
24
30
|
const ipcMessageValidator = ajv.compile(IpcMessageSchema);
|
|
25
|
-
const
|
|
31
|
+
const commandRequestValidator = ajv.compile(CommandRequestDataSchema);
|
|
26
32
|
|
|
27
33
|
export class PluginProcess {
|
|
28
34
|
childProcess: ChildProcess
|
|
@@ -108,17 +114,17 @@ export class PluginProcess {
|
|
|
108
114
|
throw new Error(`Invalid message from plugin. ${JSON.stringify(message, null, 2)}`);
|
|
109
115
|
}
|
|
110
116
|
|
|
111
|
-
if (message.cmd === MessageCmd.
|
|
117
|
+
if (message.cmd === MessageCmd.COMMAND_REQUEST) {
|
|
112
118
|
const { data, requestId } = message;
|
|
113
|
-
if (!
|
|
114
|
-
throw new Error(`Invalid sudo request from plugin. ${JSON.stringify(
|
|
119
|
+
if (!commandRequestValidator(data)) {
|
|
120
|
+
throw new Error(`Invalid sudo request from plugin. ${JSON.stringify(commandRequestValidator.errors, null, 2)}`);
|
|
115
121
|
}
|
|
116
122
|
|
|
117
|
-
const { command, options } = data as unknown as
|
|
118
|
-
const result = await
|
|
123
|
+
const { command, options } = data as unknown as CommandRequestData;
|
|
124
|
+
const result = await spawnSafe(command, options);
|
|
119
125
|
|
|
120
126
|
process.send(<IpcMessageV2>{
|
|
121
|
-
cmd: MessageCmd.
|
|
127
|
+
cmd: MessageCmd.COMMAND_REQUEST + '_Response',
|
|
122
128
|
data: result,
|
|
123
129
|
requestId,
|
|
124
130
|
})
|
|
@@ -126,8 +132,8 @@ export class PluginProcess {
|
|
|
126
132
|
|
|
127
133
|
if (message.cmd === MessageCmd.PRESS_KEY_TO_CONTINUE_REQUEST) {
|
|
128
134
|
const { data, requestId } = message;
|
|
129
|
-
if (!
|
|
130
|
-
throw new Error(`Invalid sudo request from plugin. ${JSON.stringify(
|
|
135
|
+
if (!commandRequestValidator(data)) {
|
|
136
|
+
throw new Error(`Invalid sudo request from plugin. ${JSON.stringify(commandRequestValidator.errors, null, 2)}`);
|
|
131
137
|
}
|
|
132
138
|
|
|
133
139
|
process.send(<IpcMessageV2>{
|
|
@@ -160,59 +166,3 @@ export class PluginProcess {
|
|
|
160
166
|
}
|
|
161
167
|
|
|
162
168
|
}
|
|
163
|
-
|
|
164
|
-
type CodifySpawnOptions = {
|
|
165
|
-
cwd?: string;
|
|
166
|
-
throws?: boolean,
|
|
167
|
-
} & Omit<SpawnOptions, 'detached' | 'shell' | 'stdio'>
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
*
|
|
171
|
-
* @param cmd Command to run. Ex: `rm -rf`
|
|
172
|
-
* @param opts Options for spawn
|
|
173
|
-
*
|
|
174
|
-
* @see promiseSpawn
|
|
175
|
-
* @see spawn
|
|
176
|
-
*
|
|
177
|
-
* @returns SpawnResult { status: SUCCESS | ERROR; data: string }
|
|
178
|
-
*/
|
|
179
|
-
async function sudoSpawn(
|
|
180
|
-
cmd: string,
|
|
181
|
-
opts: CodifySpawnOptions,
|
|
182
|
-
): Promise<{ data: string, status: SpawnStatus }> {
|
|
183
|
-
return new Promise((resolve) => {
|
|
184
|
-
const output: string[] = [];
|
|
185
|
-
|
|
186
|
-
const _cmd = `sudo ${cmd}`;
|
|
187
|
-
|
|
188
|
-
// Source start up shells to emulate a users environment vs. a non-interactive non-login shell script
|
|
189
|
-
// Ignore all stdin
|
|
190
|
-
const _process = spawn(`source ~/.zshrc; ${_cmd}`, [], {
|
|
191
|
-
...opts,
|
|
192
|
-
shell: 'zsh',
|
|
193
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
const { stderr, stdout } = _process
|
|
197
|
-
stdout.setEncoding('utf8');
|
|
198
|
-
stderr.setEncoding('utf8');
|
|
199
|
-
|
|
200
|
-
stdout.on('data', (data) => {
|
|
201
|
-
output.push(data.toString());
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
stderr.on('data', (data) => {
|
|
205
|
-
output.push(data.toString());
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
stdout.pipe(process.stdout);
|
|
209
|
-
stderr.pipe(process.stderr);
|
|
210
|
-
|
|
211
|
-
_process.on('close', (code) => {
|
|
212
|
-
resolve({
|
|
213
|
-
data: output.join(''),
|
|
214
|
-
status: code === 0 ? SpawnStatus.SUCCESS : SpawnStatus.ERROR,
|
|
215
|
-
})
|
|
216
|
-
})
|
|
217
|
-
})
|
|
218
|
-
}
|
package/src/shell.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export enum Shell {
|
|
2
|
+
ZSH = 'zsh',
|
|
3
|
+
BASH = 'bash',
|
|
4
|
+
SH = 'sh',
|
|
5
|
+
KSH = 'ksh',
|
|
6
|
+
CSH = 'csh',
|
|
7
|
+
FISH = 'fish',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export const ShellUtils = {
|
|
12
|
+
getShell(): Shell | undefined {
|
|
13
|
+
const shell = process.env.SHELL || '';
|
|
14
|
+
|
|
15
|
+
if (shell.endsWith('bash')) {
|
|
16
|
+
return Shell.BASH
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (shell.endsWith('zsh')) {
|
|
20
|
+
return Shell.ZSH
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (shell.endsWith('sh')) {
|
|
24
|
+
return Shell.SH
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (shell.endsWith('csh')) {
|
|
28
|
+
return Shell.CSH
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (shell.endsWith('ksh')) {
|
|
32
|
+
return Shell.KSH
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (shell.endsWith('fish')) {
|
|
36
|
+
return Shell.FISH
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return undefined;
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
getDefaultShell(): string {
|
|
43
|
+
return process.env.SHELL!;
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/spawn.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as pty from '@homebridge/node-pty-prebuilt-multiarch';
|
|
2
|
+
import { SpawnStatus } from 'codify-schemas';
|
|
3
|
+
import stripAnsi from 'strip-ansi';
|
|
4
|
+
|
|
5
|
+
import { Shell, ShellUtils } from './shell.js';
|
|
6
|
+
|
|
7
|
+
export interface SpawnResult {
|
|
8
|
+
status: SpawnStatus;
|
|
9
|
+
exitCode: number;
|
|
10
|
+
data: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SpawnOptions {
|
|
14
|
+
cwd?: string;
|
|
15
|
+
env?: Record<string, unknown>,
|
|
16
|
+
interactive?: boolean,
|
|
17
|
+
requiresRoot?: boolean,
|
|
18
|
+
stdin?: boolean,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function spawnSafe(cmd: string, options?: SpawnOptions): Promise<SpawnResult> {
|
|
22
|
+
if (cmd.toLowerCase().includes('sudo')) {
|
|
23
|
+
throw new Error('Command must not include sudo')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
process.stdout.write(`Running command: ${options?.requiresRoot ? 'sudo' : ''} ${cmd}` + (options?.cwd ? `(${options?.cwd})` : ''))
|
|
27
|
+
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
const output: string[] = [];
|
|
30
|
+
const historyIgnore = ShellUtils.getShell() === Shell.ZSH ? { HISTORY_IGNORE: '*' } : { HISTIGNORE: '*' };
|
|
31
|
+
|
|
32
|
+
// If TERM_PROGRAM=Apple_Terminal is set then ANSI escape characters may be included
|
|
33
|
+
// in the response.
|
|
34
|
+
const env = {
|
|
35
|
+
...process.env, ...options?.env,
|
|
36
|
+
TERM_PROGRAM: 'codify',
|
|
37
|
+
COMMAND_MODE: 'unix2003',
|
|
38
|
+
COLORTERM: 'truecolor',
|
|
39
|
+
...historyIgnore
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Initial terminal dimensions
|
|
43
|
+
const initialCols = process.stdout.columns ?? 80;
|
|
44
|
+
const initialRows = process.stdout.rows ?? 24;
|
|
45
|
+
|
|
46
|
+
const command = options?.requiresRoot ? `sudo ${cmd}` : cmd;
|
|
47
|
+
const args = options?.interactive ? ['-i', '-c', command] : ['-c', command]
|
|
48
|
+
|
|
49
|
+
// Run the command in a pty for interactivity
|
|
50
|
+
const mPty = pty.spawn(ShellUtils.getDefaultShell(), args, {
|
|
51
|
+
...options,
|
|
52
|
+
cols: initialCols,
|
|
53
|
+
rows: initialRows,
|
|
54
|
+
env
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
mPty.onData((data) => {
|
|
58
|
+
process.stdout.write(data);
|
|
59
|
+
output.push(data.toString());
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const resizeListener = () => {
|
|
63
|
+
const { columns, rows } = process.stdout;
|
|
64
|
+
mPty.resize(columns, rows);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const stdinListener = (data: any) => {
|
|
68
|
+
// console.log('stdinListener', data);
|
|
69
|
+
mPty.write(data.toString());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Listen to resize events for the terminal window;
|
|
73
|
+
process.stdout.on('resize', resizeListener);
|
|
74
|
+
if (options?.stdin) {
|
|
75
|
+
process.stdin.on('data', stdinListener)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
mPty.onExit((result) => {
|
|
79
|
+
process.stdout.off('resize', resizeListener);
|
|
80
|
+
if (options?.stdin) {
|
|
81
|
+
process.stdin.off('data', stdinListener);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
resolve({
|
|
85
|
+
status: result.exitCode === 0 ? SpawnStatus.SUCCESS : SpawnStatus.ERROR,
|
|
86
|
+
exitCode: result.exitCode,
|
|
87
|
+
data: stripAnsi(output.join('\n').trim()),
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
}
|