@onivoro/server-process 24.0.0 → 24.0.1
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 +262 -399
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @onivoro/server-process
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A lightweight Node.js process management library providing utilities for command execution, Docker container operations, PostgreSQL commands, and reactive process handling.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -10,517 +10,380 @@ npm install @onivoro/server-process
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
- **Command Execution**:
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **JSON Processing**: Parse command output as JSON
|
|
13
|
+
- **Promise-based Command Execution**: Simple async/await command execution
|
|
14
|
+
- **Reactive Process Streams**: RxJS-based reactive command execution
|
|
15
|
+
- **Docker Container Support**: Execute commands within Docker containers
|
|
16
|
+
- **PostgreSQL Integration**: Execute psql commands with container support
|
|
17
|
+
- **Process I/O Management**: Handle stdin/stdout streams reactively
|
|
18
|
+
- **JSON Output Processing**: Parse command output as JSON
|
|
19
19
|
- **Line-by-Line Processing**: Stream processing for large outputs
|
|
20
|
-
- **Error Handling**: Comprehensive error handling for process operations
|
|
21
|
-
- **Cross-Platform**: Works on Windows, macOS, and Linux
|
|
22
20
|
|
|
23
|
-
##
|
|
21
|
+
## API Reference
|
|
24
22
|
|
|
25
|
-
###
|
|
23
|
+
### Command Execution
|
|
24
|
+
|
|
25
|
+
#### `execPromise(cmd, options?)`
|
|
26
|
+
|
|
27
|
+
Execute a command and return a promise that resolves to stdout as a string:
|
|
26
28
|
|
|
27
29
|
```typescript
|
|
28
|
-
import { execPromise
|
|
30
|
+
import { execPromise } from '@onivoro/server-process';
|
|
29
31
|
|
|
30
32
|
// Simple command execution
|
|
31
|
-
const
|
|
32
|
-
console.log(
|
|
33
|
+
const output = await execPromise('ls -la');
|
|
34
|
+
console.log(output); // stdout as string
|
|
33
35
|
|
|
34
|
-
//
|
|
35
|
-
const
|
|
36
|
-
console.log(output);
|
|
36
|
+
// With options
|
|
37
|
+
const result = await execPromise('git status', { cwd: '/path/to/repo' });
|
|
37
38
|
```
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
import { Docker } from '@onivoro/server-process';
|
|
43
|
-
|
|
44
|
-
const docker = new Docker();
|
|
40
|
+
**Parameters:**
|
|
41
|
+
- `cmd: string` - Command to execute
|
|
42
|
+
- `options?: EncodingOption & ExecOptions` - Node.js exec options
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
const containers = await docker.listContainers();
|
|
44
|
+
**Returns:** `Promise<string>` - Command stdout
|
|
48
45
|
|
|
49
|
-
|
|
50
|
-
await docker.run('nginx:latest', {
|
|
51
|
-
ports: ['80:80'],
|
|
52
|
-
detach: true,
|
|
53
|
-
name: 'my-nginx'
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// Execute command in container
|
|
57
|
-
const result = await docker.exec('my-nginx', 'ls /usr/share/nginx/html');
|
|
58
|
-
```
|
|
46
|
+
#### `spawnPromise(program, args?, options?)`
|
|
59
47
|
|
|
60
|
-
|
|
48
|
+
Spawn a process and return a promise that resolves to joined stdout:
|
|
61
49
|
|
|
62
50
|
```typescript
|
|
63
|
-
import {
|
|
64
|
-
|
|
65
|
-
const psql = new PSql({
|
|
66
|
-
host: 'localhost',
|
|
67
|
-
port: 5432,
|
|
68
|
-
database: 'mydb',
|
|
69
|
-
username: 'user',
|
|
70
|
-
password: 'password'
|
|
71
|
-
});
|
|
51
|
+
import { spawnPromise } from '@onivoro/server-process';
|
|
72
52
|
|
|
73
|
-
//
|
|
74
|
-
const
|
|
53
|
+
// Spawn with arguments
|
|
54
|
+
const output = await spawnPromise('node', ['--version']);
|
|
55
|
+
console.log(output); // Node.js version
|
|
75
56
|
|
|
76
|
-
//
|
|
77
|
-
await
|
|
57
|
+
// With spawn options
|
|
58
|
+
const result = await spawnPromise('npm', ['install'], {
|
|
59
|
+
cwd: '/path/to/project',
|
|
60
|
+
env: { ...process.env, NODE_ENV: 'production' }
|
|
61
|
+
});
|
|
78
62
|
```
|
|
79
63
|
|
|
80
|
-
|
|
64
|
+
**Parameters:**
|
|
65
|
+
- `program: string` - Program to spawn
|
|
66
|
+
- `args?: string[]` - Command arguments
|
|
67
|
+
- `options?: any` - Node.js spawn options
|
|
68
|
+
|
|
69
|
+
**Returns:** `Promise<string>` - Joined stdout (rejects with stderr on error)
|
|
81
70
|
|
|
82
71
|
### Reactive Command Execution
|
|
83
72
|
|
|
84
|
-
|
|
85
|
-
import { execRx, execRxAsLines, execRxAsJson } from '@onivoro/server-process';
|
|
86
|
-
import { tap, catchError } from 'rxjs/operators';
|
|
87
|
-
import { of } from 'rxjs';
|
|
88
|
-
|
|
89
|
-
// Execute command reactively
|
|
90
|
-
execRx('ping google.com')
|
|
91
|
-
.pipe(
|
|
92
|
-
tap(data => console.log('Output:', data)),
|
|
93
|
-
catchError(error => {
|
|
94
|
-
console.error('Command failed:', error);
|
|
95
|
-
return of(null);
|
|
96
|
-
})
|
|
97
|
-
)
|
|
98
|
-
.subscribe();
|
|
99
|
-
|
|
100
|
-
// Process output line by line
|
|
101
|
-
execRxAsLines('tail -f /var/log/system.log')
|
|
102
|
-
.pipe(
|
|
103
|
-
tap(line => console.log('Log line:', line))
|
|
104
|
-
)
|
|
105
|
-
.subscribe();
|
|
106
|
-
|
|
107
|
-
// Parse JSON output
|
|
108
|
-
execRxAsJson('docker inspect my-container')
|
|
109
|
-
.pipe(
|
|
110
|
-
tap(json => console.log('Container info:', json))
|
|
111
|
-
)
|
|
112
|
-
.subscribe();
|
|
113
|
-
```
|
|
73
|
+
#### `execRx(cmd, options?, emitStdErr?)`
|
|
114
74
|
|
|
115
|
-
|
|
75
|
+
Execute a command reactively, emitting stdout (and optionally stderr):
|
|
116
76
|
|
|
117
77
|
```typescript
|
|
118
|
-
import {
|
|
78
|
+
import { execRx } from '@onivoro/server-process';
|
|
119
79
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
80
|
+
execRx('ping -c 3 google.com').subscribe({
|
|
81
|
+
next: (output) => console.log('Output:', output),
|
|
82
|
+
error: (err) => console.error('Error:', err),
|
|
83
|
+
complete: () => console.log('Command completed')
|
|
124
84
|
});
|
|
125
85
|
|
|
126
|
-
|
|
127
|
-
|
|
86
|
+
// Without stderr
|
|
87
|
+
execRx('ls -la', undefined, false).subscribe(console.log);
|
|
128
88
|
```
|
|
129
89
|
|
|
130
|
-
|
|
90
|
+
**Parameters:**
|
|
91
|
+
- `cmd: string` - Command to execute
|
|
92
|
+
- `options?: EncodingOption & ExecOptions` - Node.js exec options
|
|
93
|
+
- `emitStdErr?: boolean` - Include stderr in output (default: true)
|
|
131
94
|
|
|
132
|
-
|
|
133
|
-
import { Docker } from '@onivoro/server-process';
|
|
95
|
+
**Returns:** `Observable<string>` - Command output stream
|
|
134
96
|
|
|
135
|
-
|
|
136
|
-
private docker = new Docker();
|
|
97
|
+
#### `execRxAsLines(cmd, options?, emitStdErr?)`
|
|
137
98
|
|
|
138
|
-
|
|
139
|
-
// Pull latest image
|
|
140
|
-
await this.docker.pull(imageName);
|
|
99
|
+
Execute a command and emit output line by line:
|
|
141
100
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
await this.docker.stop(config.containerName);
|
|
145
|
-
await this.docker.remove(config.containerName);
|
|
146
|
-
} catch (error) {
|
|
147
|
-
// Container might not exist, continue
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Run new container
|
|
151
|
-
const containerId = await this.docker.run(imageName, {
|
|
152
|
-
name: config.containerName,
|
|
153
|
-
ports: config.ports,
|
|
154
|
-
volumes: config.volumes,
|
|
155
|
-
env: config.environment,
|
|
156
|
-
detach: true,
|
|
157
|
-
restart: 'unless-stopped'
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
return containerId;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async getContainerLogs(containerName: string): Promise<string> {
|
|
164
|
-
return this.docker.logs(containerName, { tail: 100 });
|
|
165
|
-
}
|
|
101
|
+
```typescript
|
|
102
|
+
import { execRxAsLines } from '@onivoro/server-process';
|
|
166
103
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
} catch (error) {
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
104
|
+
execRxAsLines('cat large-file.txt').subscribe({
|
|
105
|
+
next: (line) => console.log('Line:', line),
|
|
106
|
+
complete: () => console.log('File processed')
|
|
107
|
+
});
|
|
176
108
|
```
|
|
177
109
|
|
|
178
|
-
|
|
110
|
+
**Parameters:**
|
|
111
|
+
- `cmd: string` - Command to execute
|
|
112
|
+
- `options?: ExecOptions` - Node.js exec options
|
|
113
|
+
- `emitStdErr?: boolean` - Include stderr in output (default: true)
|
|
179
114
|
|
|
180
|
-
|
|
181
|
-
import { PSql } from '@onivoro/server-process';
|
|
182
|
-
import { readdir } from 'fs/promises';
|
|
183
|
-
import { join } from 'path';
|
|
115
|
+
**Returns:** `Observable<string>` - Stream of individual lines
|
|
184
116
|
|
|
185
|
-
|
|
186
|
-
private psql: PSql;
|
|
117
|
+
#### `execRxAsJson(cmd, options?, emitStdErr?)`
|
|
187
118
|
|
|
188
|
-
|
|
189
|
-
this.psql = new PSql(connectionConfig);
|
|
190
|
-
}
|
|
119
|
+
Execute a command and parse output as JSON:
|
|
191
120
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
await this.psql.query(`
|
|
195
|
-
CREATE TABLE IF NOT EXISTS migrations (
|
|
196
|
-
id SERIAL PRIMARY KEY,
|
|
197
|
-
filename VARCHAR(255) NOT NULL,
|
|
198
|
-
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
199
|
-
)
|
|
200
|
-
`);
|
|
201
|
-
|
|
202
|
-
// Get executed migrations
|
|
203
|
-
const executedMigrations = await this.psql.query(
|
|
204
|
-
'SELECT filename FROM migrations ORDER BY id'
|
|
205
|
-
);
|
|
206
|
-
const executedFiles = executedMigrations.map(row => row.filename);
|
|
207
|
-
|
|
208
|
-
// Get migration files
|
|
209
|
-
const files = await readdir(migrationsDir);
|
|
210
|
-
const migrationFiles = files
|
|
211
|
-
.filter(file => file.endsWith('.sql'))
|
|
212
|
-
.sort();
|
|
213
|
-
|
|
214
|
-
// Execute pending migrations
|
|
215
|
-
for (const file of migrationFiles) {
|
|
216
|
-
if (!executedFiles.includes(file)) {
|
|
217
|
-
console.log(`Executing migration: ${file}`);
|
|
218
|
-
|
|
219
|
-
try {
|
|
220
|
-
await this.psql.executeFile(join(migrationsDir, file));
|
|
221
|
-
await this.psql.query(
|
|
222
|
-
'INSERT INTO migrations (filename) VALUES ($1)',
|
|
223
|
-
[file]
|
|
224
|
-
);
|
|
225
|
-
console.log(`Migration ${file} completed successfully`);
|
|
226
|
-
} catch (error) {
|
|
227
|
-
console.error(`Migration ${file} failed:`, error);
|
|
228
|
-
throw error;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
121
|
+
```typescript
|
|
122
|
+
import { execRxAsJson } from '@onivoro/server-process';
|
|
233
123
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
for (const migration of migrations) {
|
|
241
|
-
// Execute rollback script if exists
|
|
242
|
-
const rollbackFile = migration.filename.replace('.sql', '.rollback.sql');
|
|
243
|
-
try {
|
|
244
|
-
await this.psql.executeFile(rollbackFile);
|
|
245
|
-
await this.psql.query(
|
|
246
|
-
'DELETE FROM migrations WHERE filename = $1',
|
|
247
|
-
[migration.filename]
|
|
248
|
-
);
|
|
249
|
-
console.log(`Rolled back migration: ${migration.filename}`);
|
|
250
|
-
} catch (error) {
|
|
251
|
-
console.error(`Rollback failed for ${migration.filename}:`, error);
|
|
252
|
-
throw error;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
124
|
+
execRxAsJson('docker inspect my-container').subscribe({
|
|
125
|
+
next: (json) => console.log('Container info:', json),
|
|
126
|
+
error: (err) => console.error('Failed to parse JSON:', err)
|
|
127
|
+
});
|
|
257
128
|
```
|
|
258
129
|
|
|
259
|
-
|
|
130
|
+
**Parameters:**
|
|
131
|
+
- `cmd: string` - Command to execute
|
|
132
|
+
- `options?: ExecOptions` - Node.js exec options
|
|
133
|
+
- `emitStdErr?: boolean` - Include stderr in output (default: true)
|
|
260
134
|
|
|
261
|
-
|
|
262
|
-
import { execPromise, execRxAsLines } from '@onivoro/server-process';
|
|
135
|
+
**Returns:** `Observable<any>` - Stream of parsed JSON objects
|
|
263
136
|
|
|
264
|
-
|
|
265
|
-
async getSystemInfo(): Promise<any> {
|
|
266
|
-
const [cpu, memory, disk] = await Promise.all([
|
|
267
|
-
this.getCpuInfo(),
|
|
268
|
-
this.getMemoryInfo(),
|
|
269
|
-
this.getDiskInfo()
|
|
270
|
-
]);
|
|
137
|
+
### Docker Class
|
|
271
138
|
|
|
272
|
-
|
|
273
|
-
}
|
|
139
|
+
Execute commands within Docker containers:
|
|
274
140
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
// Parse CPU information
|
|
278
|
-
return this.parseCpuInfo(result.stdout);
|
|
279
|
-
}
|
|
141
|
+
```typescript
|
|
142
|
+
import { Docker } from '@onivoro/server-process';
|
|
280
143
|
|
|
281
|
-
|
|
282
|
-
const result = await execPromise('free -m');
|
|
283
|
-
return this.parseMemoryInfo(result.stdout);
|
|
284
|
-
}
|
|
144
|
+
const docker = new Docker('my-postgres-container', 'psql');
|
|
285
145
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
146
|
+
// Execute psql command in container
|
|
147
|
+
docker.execRx('-c "SELECT version();"').subscribe({
|
|
148
|
+
next: (result) => console.log('DB Version:', result),
|
|
149
|
+
error: (err) => console.error('Query failed:', err)
|
|
150
|
+
});
|
|
151
|
+
```
|
|
290
152
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (this.isErrorLog(line)) {
|
|
295
|
-
this.handleSystemError(line);
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
}
|
|
153
|
+
**Constructor:**
|
|
154
|
+
- `containerName: string` - Name of the Docker container
|
|
155
|
+
- `binaryName: string` - Binary to execute within the container
|
|
299
156
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
line.toLowerCase().includes('fail');
|
|
303
|
-
}
|
|
157
|
+
**Methods:**
|
|
158
|
+
- `execRx(cmd, options?, emitStdErr?)` - Execute command reactively within container
|
|
304
159
|
|
|
305
|
-
|
|
306
|
-
console.error('System error detected:', errorLine);
|
|
307
|
-
// Implement alerting logic
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
```
|
|
160
|
+
### PSql Class
|
|
311
161
|
|
|
312
|
-
|
|
162
|
+
Execute PostgreSQL commands with optional Docker container support:
|
|
313
163
|
|
|
314
164
|
```typescript
|
|
315
|
-
import {
|
|
165
|
+
import { PSql } from '@onivoro/server-process';
|
|
316
166
|
|
|
317
|
-
|
|
318
|
-
|
|
167
|
+
// Local psql
|
|
168
|
+
const psql = new PSql();
|
|
169
|
+
psql.execRx('SELECT COUNT(*) FROM users;', 'mydb', 'postgres').subscribe(console.log);
|
|
319
170
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
171
|
+
// Container-based psql
|
|
172
|
+
const containerPsql = new PSql('postgres-container');
|
|
173
|
+
containerPsql.execRx('SELECT NOW();', 'mydb', 'postgres').subscribe(console.log);
|
|
174
|
+
```
|
|
323
175
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
listen('SIGTERM', async () => {
|
|
327
|
-
console.log('Received SIGTERM, shutting down gracefully...');
|
|
328
|
-
await this.performCleanup();
|
|
329
|
-
exit(0);
|
|
330
|
-
});
|
|
176
|
+
**Constructor:**
|
|
177
|
+
- `containerName?: string` - Optional Docker container name
|
|
331
178
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
179
|
+
**Methods:**
|
|
180
|
+
- `execRx(cmd, db, username)` - Execute psql command
|
|
181
|
+
- `cmd: string` - SQL command to execute
|
|
182
|
+
- `db: string` - Database name
|
|
183
|
+
- `username: string` - PostgreSQL username
|
|
337
184
|
|
|
338
|
-
|
|
339
|
-
process.on('uncaughtException', async (error) => {
|
|
340
|
-
console.error('Uncaught exception:', error);
|
|
341
|
-
await this.performCleanup();
|
|
342
|
-
exit(1);
|
|
343
|
-
});
|
|
344
|
-
}
|
|
185
|
+
### Process I/O Management
|
|
345
186
|
|
|
346
|
-
|
|
347
|
-
console.log('Performing cleanup tasks...');
|
|
348
|
-
|
|
349
|
-
await Promise.all(
|
|
350
|
-
this.cleanup.map(async (task, index) => {
|
|
351
|
-
try {
|
|
352
|
-
await task();
|
|
353
|
-
console.log(`Cleanup task ${index + 1} completed`);
|
|
354
|
-
} catch (error) {
|
|
355
|
-
console.error(`Cleanup task ${index + 1} failed:`, error);
|
|
356
|
-
}
|
|
357
|
-
})
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
187
|
+
#### `listen()`
|
|
361
188
|
|
|
362
|
-
|
|
363
|
-
const shutdown = new GracefulShutdown();
|
|
189
|
+
Create reactive streams for process stdin/stdout:
|
|
364
190
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
// Close database connections
|
|
368
|
-
await database.close();
|
|
369
|
-
});
|
|
191
|
+
```typescript
|
|
192
|
+
import { listen } from '@onivoro/server-process';
|
|
370
193
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
194
|
+
const { stdout, stdin } = listen();
|
|
195
|
+
|
|
196
|
+
// Handle stdin data
|
|
197
|
+
stdin.subscribe((data) => {
|
|
198
|
+
console.log('Received input:', data.toString());
|
|
374
199
|
});
|
|
375
200
|
|
|
376
|
-
|
|
201
|
+
// stdin automatically completes when process.stdin closes
|
|
377
202
|
```
|
|
378
203
|
|
|
379
|
-
|
|
204
|
+
**Returns:** `{ stdout: Subject, stdin: Subject }` - Reactive streams for process I/O
|
|
380
205
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
#### execPromise(command, options?)
|
|
206
|
+
#### `exit(code)`
|
|
384
207
|
|
|
385
|
-
|
|
208
|
+
Create a process exit function with specified exit code:
|
|
386
209
|
|
|
387
210
|
```typescript
|
|
388
|
-
|
|
389
|
-
stdout: string;
|
|
390
|
-
stderr: string;
|
|
391
|
-
code: number;
|
|
392
|
-
}
|
|
211
|
+
import { exit } from '@onivoro/server-process';
|
|
393
212
|
|
|
394
|
-
|
|
395
|
-
|
|
213
|
+
const exitWithError = exit(1);
|
|
214
|
+
const exitSuccess = exit(0);
|
|
396
215
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
function spawnPromise(
|
|
403
|
-
command: string,
|
|
404
|
-
args?: string[],
|
|
405
|
-
options?: SpawnOptions
|
|
406
|
-
): Promise<SpawnResult>
|
|
216
|
+
// Use in error handling
|
|
217
|
+
if (someErrorCondition) {
|
|
218
|
+
console.error('Fatal error occurred');
|
|
219
|
+
exitWithError();
|
|
220
|
+
}
|
|
407
221
|
```
|
|
408
222
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
#### execRx(command, options?)
|
|
223
|
+
**Parameters:**
|
|
224
|
+
- `code: number` - Exit code
|
|
412
225
|
|
|
413
|
-
|
|
226
|
+
**Returns:** `() => never` - Function that exits the process
|
|
414
227
|
|
|
415
|
-
|
|
416
|
-
function execRx(command: string, options?: ExecOptions): Observable<string>
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
#### execRxAsLines(command, options?)
|
|
228
|
+
## Usage Examples
|
|
420
229
|
|
|
421
|
-
|
|
230
|
+
### Basic Command Execution
|
|
422
231
|
|
|
423
232
|
```typescript
|
|
424
|
-
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
#### execRxAsJson(command, options?)
|
|
428
|
-
|
|
429
|
-
Execute command and parse output as JSON:
|
|
233
|
+
import { execPromise, spawnPromise } from '@onivoro/server-process';
|
|
430
234
|
|
|
431
|
-
|
|
432
|
-
|
|
235
|
+
async function deployApp() {
|
|
236
|
+
try {
|
|
237
|
+
// Check if Docker is running
|
|
238
|
+
await execPromise('docker --version');
|
|
239
|
+
|
|
240
|
+
// Build and deploy
|
|
241
|
+
const buildOutput = await spawnPromise('docker', ['build', '-t', 'myapp', '.']);
|
|
242
|
+
console.log('Build completed:', buildOutput);
|
|
243
|
+
|
|
244
|
+
const runOutput = await spawnPromise('docker', ['run', '-d', '-p', '3000:3000', 'myapp']);
|
|
245
|
+
console.log('Container started:', runOutput);
|
|
246
|
+
} catch (error) {
|
|
247
|
+
console.error('Deployment failed:', error);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
433
250
|
```
|
|
434
251
|
|
|
435
|
-
###
|
|
436
|
-
|
|
437
|
-
Docker container management:
|
|
252
|
+
### Reactive Log Monitoring
|
|
438
253
|
|
|
439
254
|
```typescript
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
255
|
+
import { execRxAsLines } from '@onivoro/server-process';
|
|
256
|
+
|
|
257
|
+
function monitorLogs(logFile: string) {
|
|
258
|
+
execRxAsLines(`tail -f ${logFile}`)
|
|
259
|
+
.subscribe({
|
|
260
|
+
next: (line) => {
|
|
261
|
+
if (line.includes('ERROR')) {
|
|
262
|
+
console.error('🚨 Error detected:', line);
|
|
263
|
+
// Trigger alert
|
|
264
|
+
} else if (line.includes('WARN')) {
|
|
265
|
+
console.warn('⚠️ Warning:', line);
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
error: (err) => console.error('Log monitoring failed:', err)
|
|
269
|
+
});
|
|
448
270
|
}
|
|
449
|
-
```
|
|
450
271
|
|
|
451
|
-
|
|
272
|
+
monitorLogs('/var/log/app.log');
|
|
273
|
+
```
|
|
452
274
|
|
|
453
|
-
|
|
275
|
+
### Database Operations with Docker
|
|
454
276
|
|
|
455
277
|
```typescript
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
278
|
+
import { PSql } from '@onivoro/server-process';
|
|
279
|
+
|
|
280
|
+
class DatabaseManager {
|
|
281
|
+
private psql = new PSql('postgres-container');
|
|
282
|
+
|
|
283
|
+
async checkHealth() {
|
|
284
|
+
return new Promise((resolve, reject) => {
|
|
285
|
+
this.psql.execRx('SELECT 1;', 'postgres', 'admin').subscribe({
|
|
286
|
+
next: (result) => {
|
|
287
|
+
console.log('Database is healthy');
|
|
288
|
+
resolve(result);
|
|
289
|
+
},
|
|
290
|
+
error: reject
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async getUserCount(database: string) {
|
|
296
|
+
return new Promise((resolve, reject) => {
|
|
297
|
+
this.psql.execRx('SELECT COUNT(*) FROM users;', database, 'admin').subscribe({
|
|
298
|
+
next: (result) => resolve(parseInt(result.trim())),
|
|
299
|
+
error: reject
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
}
|
|
461
303
|
}
|
|
462
304
|
```
|
|
463
305
|
|
|
464
|
-
###
|
|
306
|
+
### JSON Processing
|
|
465
307
|
|
|
466
|
-
|
|
308
|
+
```typescript
|
|
309
|
+
import { execRxAsJson } from '@onivoro/server-process';
|
|
467
310
|
|
|
468
|
-
|
|
311
|
+
interface ContainerInfo {
|
|
312
|
+
Id: string;
|
|
313
|
+
Name: string;
|
|
314
|
+
State: { Status: string };
|
|
315
|
+
}
|
|
469
316
|
|
|
470
|
-
|
|
471
|
-
|
|
317
|
+
function getContainerStatus(containerName: string) {
|
|
318
|
+
execRxAsJson<ContainerInfo[]>(`docker inspect ${containerName}`).subscribe({
|
|
319
|
+
next: (containers) => {
|
|
320
|
+
const container = containers[0];
|
|
321
|
+
console.log(`Container ${container.Name} is ${container.State.Status}`);
|
|
322
|
+
},
|
|
323
|
+
error: (err) => console.error('Failed to get container info:', err)
|
|
324
|
+
});
|
|
325
|
+
}
|
|
472
326
|
```
|
|
473
327
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
Exit process with optional code:
|
|
328
|
+
### Process I/O Handling
|
|
477
329
|
|
|
478
330
|
```typescript
|
|
479
|
-
|
|
331
|
+
import { listen, exit } from '@onivoro/server-process';
|
|
332
|
+
|
|
333
|
+
function setupInteractiveMode() {
|
|
334
|
+
const { stdin } = listen();
|
|
335
|
+
|
|
336
|
+
console.log('Enter commands (type "exit" to quit):');
|
|
337
|
+
|
|
338
|
+
stdin.subscribe({
|
|
339
|
+
next: (data) => {
|
|
340
|
+
const input = data.toString().trim();
|
|
341
|
+
|
|
342
|
+
if (input === 'exit') {
|
|
343
|
+
console.log('Goodbye!');
|
|
344
|
+
exit(0)();
|
|
345
|
+
} else {
|
|
346
|
+
console.log(`You entered: ${input}`);
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
complete: () => {
|
|
350
|
+
console.log('Input stream closed');
|
|
351
|
+
exit(0)();
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}
|
|
480
355
|
```
|
|
481
356
|
|
|
482
|
-
##
|
|
357
|
+
## Error Handling
|
|
483
358
|
|
|
484
|
-
|
|
485
|
-
2. **Resource Cleanup**: Use proper cleanup for long-running processes
|
|
486
|
-
3. **Signal Handling**: Implement graceful shutdown for production applications
|
|
487
|
-
4. **Stream Processing**: Use reactive streams for large outputs
|
|
488
|
-
5. **Security**: Validate input when executing external commands
|
|
489
|
-
6. **Logging**: Log process operations for debugging and monitoring
|
|
490
|
-
7. **Timeouts**: Set appropriate timeouts for long-running operations
|
|
491
|
-
8. **Environment**: Use environment-specific configurations
|
|
359
|
+
All functions handle errors appropriately:
|
|
492
360
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
2. **Privilege Escalation**: Run processes with minimal required privileges
|
|
497
|
-
3. **Environment Variables**: Be careful with sensitive environment variables
|
|
498
|
-
4. **File Permissions**: Ensure proper file permissions for executed scripts
|
|
499
|
-
5. **Container Security**: Follow Docker security best practices
|
|
500
|
-
|
|
501
|
-
## Testing
|
|
361
|
+
- **Promise-based functions** reject with error details
|
|
362
|
+
- **Reactive functions** emit errors through the error channel
|
|
363
|
+
- **Process execution errors** include exit codes and stderr information
|
|
502
364
|
|
|
503
365
|
```typescript
|
|
504
|
-
import { execPromise,
|
|
366
|
+
import { execPromise, execRx } from '@onivoro/server-process';
|
|
505
367
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
368
|
+
// Promise error handling
|
|
369
|
+
try {
|
|
370
|
+
await execPromise('nonexistent-command');
|
|
371
|
+
} catch (error) {
|
|
372
|
+
console.error('Command failed:', error.message);
|
|
373
|
+
}
|
|
512
374
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
expect(error.code).not.toBe(0);
|
|
519
|
-
}
|
|
520
|
-
});
|
|
375
|
+
// Reactive error handling
|
|
376
|
+
execRx('invalid-command').subscribe({
|
|
377
|
+
next: (output) => console.log(output),
|
|
378
|
+
error: (error) => console.error('Command error:', error),
|
|
379
|
+
complete: () => console.log('Done')
|
|
521
380
|
});
|
|
522
381
|
```
|
|
523
382
|
|
|
383
|
+
## TypeScript Support
|
|
384
|
+
|
|
385
|
+
All functions are fully typed with TypeScript interfaces. The library uses Node.js built-in types for options and return values.
|
|
386
|
+
|
|
524
387
|
## License
|
|
525
388
|
|
|
526
389
|
This library is part of the Onivoro monorepo ecosystem.
|