pty-serv 0.1.2 → 0.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/README.md CHANGED
@@ -2,19 +2,13 @@
2
2
 
3
3
  Spawns a command in a [node-pty](https://github.com/microsoft/node-pty) pseudo-terminal and exposes an HTTP endpoint to interact with it at runtime.
4
4
 
5
- ## Installation
6
-
7
- ```bash
8
- npm install -g pty-serv
9
- ```
10
-
11
5
  ## Usage
12
6
 
13
7
  Run a shell, then send a command from another process:
14
8
 
15
9
  ```bash
16
10
  # Terminal 1 – start the PTY server
17
- pty-serv -s /tmp/pty-serv-0.sock -- bash
11
+ npx pty-serv -s /tmp/pty-serv-0.sock -- bash
18
12
 
19
13
  # Terminal 2 – interact with it
20
14
  curl --unix-socket /tmp/pty-serv-0.sock 'http://[::]/write' --data $'echo hello\r'
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/cli.js CHANGED
@@ -1,7 +1,12 @@
1
+ import { unlinkSync } from 'node:fs';
1
2
  import { createServer } from 'node:http';
2
3
  import { program } from 'commander';
3
- import * as pty from 'node-pty';
4
+ import { spawn } from 'node-pty';
5
+ import { handlePtyRequest } from './request-handler.js';
4
6
  program
7
+ .enablePositionalOptions()
8
+ .passThroughOptions()
9
+ .name('pty-serv')
5
10
  .option('-o, --options <string>', 'JSON options forwarded to `node-pty`')
6
11
  .option('-s, --socket <string>', 'Unix socket path to listen on')
7
12
  .option('-h, --hostname <string>', 'Hostname for TCP listener')
@@ -10,79 +15,40 @@ program
10
15
  .parse();
11
16
  const opts = program.opts();
12
17
  const server = createServer();
13
- const ptyProcess = pty.spawn(program.args[0], program.args.slice(1), opts.options && JSON.parse(opts.options));
14
- console.log('PTY spawned with PID:', ptyProcess.pid);
15
- ptyProcess.onData((chunk) => {
16
- process.stdout.write(chunk);
17
- });
18
- let hasExit = false;
19
- ptyProcess.onExit((info) => {
20
- hasExit = true;
21
- server.close();
22
- process.exit(info.exitCode);
23
- });
24
- const EXIT_SIGNALS = ['SIGINT', 'SIGTERM'];
25
- EXIT_SIGNALS.forEach((signal) => {
26
- process.on(signal, () => {
27
- if (!hasExit) {
28
- ptyProcess.kill(process.platform === 'win32' ? undefined : signal);
29
- }
30
- server.close();
31
- process.exit(0);
18
+ server.on('listening', () => {
19
+ const ptyProcess = spawn(program.args[0], program.args.slice(1), opts.options && JSON.parse(opts.options));
20
+ ptyProcess.onData((chunk) => {
21
+ process.stdout.write(chunk);
22
+ });
23
+ ptyProcess.onExit((info) => {
24
+ server.close(() => {
25
+ process.exit(info.exitCode);
26
+ });
27
+ setTimeout(() => {
28
+ process.exit(info.exitCode);
29
+ }, 0).unref();
30
+ });
31
+ server.on('request', (req, res) => {
32
+ handlePtyRequest(req, res, ptyProcess);
32
33
  });
33
- });
34
- server.on('request', async (req, res) => {
35
- try {
36
- await handleRequest(req);
37
- res.writeHead(200, { 'Content-Type': 'application/json' });
38
- res.end(JSON.stringify({ code: 0 }));
39
- }
40
- catch (error) {
41
- res.writeHead(400, { 'Content-Type': 'application/json' });
42
- res.end(JSON.stringify({ code: -1, message: String(error) }));
43
- }
44
34
  });
45
35
  if (opts.socket) {
36
+ tryUnlink(opts.socket);
46
37
  server.listen(opts.socket);
47
38
  }
48
- else {
39
+ else if (opts.port != null) {
49
40
  server.listen(opts.port, opts.hostname);
50
41
  }
51
- async function handleRequest(req) {
52
- switch (req.url) {
53
- case '/resize': {
54
- const data = await parseBody(req);
55
- ptyProcess.resize(data.cols, data.rows);
56
- return;
57
- }
58
- case '/clear':
59
- ptyProcess.clear();
60
- return;
61
- case '/write':
62
- for await (const chunk of req) {
63
- ptyProcess.write(chunk);
64
- }
65
- return;
66
- case '/kill': {
67
- const data = await parseBody(req);
68
- ptyProcess.kill(data.signal);
69
- return;
70
- }
71
- case '/pause':
72
- ptyProcess.pause();
73
- return;
74
- case '/resume':
75
- ptyProcess.resume();
76
- return;
77
- }
42
+ else {
43
+ program
44
+ .addHelpText('after', '\nEither --socket or --port must be specified')
45
+ .help({ error: true });
78
46
  }
79
- async function parseBody(req) {
80
- if (req.method !== 'POST') {
81
- throw new Error(`Can't parse body for ${req.method} request`);
47
+ function tryUnlink(filename) {
48
+ try {
49
+ unlinkSync(filename);
82
50
  }
83
- const chunks = [];
84
- for await (const chunk of req) {
85
- chunks.push(chunk);
51
+ catch {
52
+ // noop
86
53
  }
87
- return JSON.parse(Buffer.concat(chunks).toString());
88
54
  }
@@ -0,0 +1,2 @@
1
+ export * from './request-handler.js';
2
+ export * from './spawn.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './request-handler.js';
2
+ export * from './spawn.js';
@@ -0,0 +1,3 @@
1
+ import { IncomingMessage, ServerResponse } from 'http';
2
+ import { IPty } from 'node-pty';
3
+ export declare function handlePtyRequest(req: IncomingMessage, res: ServerResponse, ptyProcess: IPty): Promise<void>;
@@ -0,0 +1,49 @@
1
+ export async function handlePtyRequest(req, res, ptyProcess) {
2
+ try {
3
+ await handleRequest(ptyProcess, req);
4
+ res.writeHead(200, { 'Content-Type': 'application/json' });
5
+ res.end(JSON.stringify({ code: 0 }));
6
+ }
7
+ catch (error) {
8
+ res.writeHead(400, { 'Content-Type': 'application/json' });
9
+ res.end(JSON.stringify({ code: -1, message: String(error) }));
10
+ }
11
+ }
12
+ async function handleRequest(ptyProcess, req) {
13
+ switch (req.url) {
14
+ case '/resize': {
15
+ const data = await parseBody(req);
16
+ ptyProcess.resize(data.cols, data.rows);
17
+ return;
18
+ }
19
+ case '/clear':
20
+ ptyProcess.clear();
21
+ return;
22
+ case '/write':
23
+ for await (const chunk of req) {
24
+ ptyProcess.write(chunk);
25
+ }
26
+ return;
27
+ case '/kill': {
28
+ const data = await parseBody(req);
29
+ ptyProcess.kill(data.signal);
30
+ return;
31
+ }
32
+ case '/pause':
33
+ ptyProcess.pause();
34
+ return;
35
+ case '/resume':
36
+ ptyProcess.resume();
37
+ return;
38
+ }
39
+ }
40
+ async function parseBody(req) {
41
+ if (req.method !== 'POST') {
42
+ throw new Error(`Can't parse body for ${req.method} request`);
43
+ }
44
+ const chunks = [];
45
+ for await (const chunk of req) {
46
+ chunks.push(chunk);
47
+ }
48
+ return JSON.parse(Buffer.concat(chunks).toString());
49
+ }
@@ -0,0 +1,20 @@
1
+ import { IPtyForkOptions, IWindowsPtyForkOptions } from 'node-pty';
2
+ export interface SpawnPtyOptions {
3
+ /**
4
+ * Options forwarded to `node-pty`
5
+ */
6
+ ptyOptions?: IPtyForkOptions | IWindowsPtyForkOptions;
7
+ /**
8
+ * Unix socket path to listen on
9
+ */
10
+ socket?: string;
11
+ /**
12
+ * Hostname for TCP listener
13
+ */
14
+ hostname?: string;
15
+ /**
16
+ * TCP port to listen on
17
+ */
18
+ port?: number;
19
+ }
20
+ export declare function getSpawnPtyArgs(command: string, args?: string[], options?: SpawnPtyOptions): string[];
package/dist/spawn.js ADDED
@@ -0,0 +1,21 @@
1
+ import path from 'node:path';
2
+ export function getSpawnPtyArgs(command, args, options) {
3
+ const nodeArgs = [path.resolve(import.meta.dirname, 'cli.js')];
4
+ if (options?.ptyOptions) {
5
+ nodeArgs.push('--options', JSON.stringify(options.ptyOptions));
6
+ }
7
+ if (options?.socket) {
8
+ nodeArgs.push('--socket', options.socket);
9
+ }
10
+ if (options?.hostname) {
11
+ nodeArgs.push('--hostname', options.hostname);
12
+ }
13
+ if (options?.port != null) {
14
+ nodeArgs.push('--port', String(options.port));
15
+ }
16
+ nodeArgs.push(command);
17
+ if (args) {
18
+ nodeArgs.push(...args);
19
+ }
20
+ return nodeArgs;
21
+ }
package/package.json CHANGED
@@ -1,13 +1,22 @@
1
1
  {
2
2
  "name": "pty-serv",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/gzzhanghao/node-pty-serv.git"
7
7
  },
8
8
  "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "main": "./dist/index.js",
16
+ "module": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
9
18
  "bin": {
10
- "pty-serv": "./bin/index.js"
19
+ "pty-serv": "./bin/cli.js"
11
20
  },
12
21
  "files": [
13
22
  "bin",