@onivoro/server-process 22.0.2 → 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 +389 -0
- package/jest.config.ts +11 -0
- package/package.json +8 -40
- package/project.json +23 -0
- package/src/lib/docker.ts +14 -0
- package/src/lib/exec-promise.spec.ts +17 -0
- package/src/lib/exec-promise.ts +14 -0
- package/src/lib/exec-rx-as-json.ts +9 -0
- package/src/lib/exec-rx-as-lines.ts +10 -0
- package/src/lib/exec-rx.spec.ts +22 -0
- package/src/lib/exec-rx.ts +16 -0
- package/src/lib/exit.ts +1 -0
- package/src/lib/listen.ts +12 -0
- package/src/lib/psql.ts +16 -0
- package/src/lib/spawn-promise.spec.ts +17 -0
- package/src/lib/spawn-promise.ts +31 -0
- package/tsconfig.json +16 -0
- package/tsconfig.lib.json +8 -0
- package/tsconfig.spec.json +21 -0
- package/dist/cjs/index.js +0 -21
- package/dist/cjs/lib/docker.d.ts +0 -8
- package/dist/cjs/lib/docker.js +0 -16
- package/dist/cjs/lib/exec-promise.d.ts +0 -3
- package/dist/cjs/lib/exec-promise.js +0 -16
- package/dist/cjs/lib/exec-rx-as-json.d.ts +0 -2
- package/dist/cjs/lib/exec-rx-as-json.js +0 -9
- package/dist/cjs/lib/exec-rx-as-lines.d.ts +0 -2
- package/dist/cjs/lib/exec-rx-as-lines.js +0 -10
- package/dist/cjs/lib/exec-rx.d.ts +0 -4
- package/dist/cjs/lib/exec-rx.js +0 -18
- package/dist/cjs/lib/exit.d.ts +0 -1
- package/dist/cjs/lib/exit.js +0 -5
- package/dist/cjs/lib/listen.d.ts +0 -5
- package/dist/cjs/lib/listen.js +0 -12
- package/dist/cjs/lib/psql.d.ts +0 -5
- package/dist/cjs/lib/psql.js +0 -20
- package/dist/cjs/lib/spawn-promise.d.ts +0 -1
- package/dist/cjs/lib/spawn-promise.js +0 -28
- package/dist/esm/index.d.ts +0 -9
- package/dist/esm/index.js +0 -21
- package/dist/esm/lib/docker.d.ts +0 -8
- package/dist/esm/lib/docker.js +0 -16
- package/dist/esm/lib/exec-promise.d.ts +0 -3
- package/dist/esm/lib/exec-promise.js +0 -16
- package/dist/esm/lib/exec-rx-as-json.d.ts +0 -2
- package/dist/esm/lib/exec-rx-as-json.js +0 -9
- package/dist/esm/lib/exec-rx-as-lines.d.ts +0 -2
- package/dist/esm/lib/exec-rx-as-lines.js +0 -10
- package/dist/esm/lib/exec-rx.d.ts +0 -4
- package/dist/esm/lib/exec-rx.js +0 -18
- package/dist/esm/lib/exit.d.ts +0 -1
- package/dist/esm/lib/exit.js +0 -5
- package/dist/esm/lib/listen.d.ts +0 -5
- package/dist/esm/lib/listen.js +0 -12
- package/dist/esm/lib/psql.d.ts +0 -5
- package/dist/esm/lib/psql.js +0 -20
- package/dist/esm/lib/spawn-promise.d.ts +0 -1
- package/dist/esm/lib/spawn-promise.js +0 -28
- package/dist/types/index.d.ts +0 -9
- package/dist/types/lib/docker.d.ts +0 -8
- package/dist/types/lib/exec-promise.d.ts +0 -3
- package/dist/types/lib/exec-rx-as-json.d.ts +0 -2
- package/dist/types/lib/exec-rx-as-lines.d.ts +0 -2
- package/dist/types/lib/exec-rx.d.ts +0 -4
- package/dist/types/lib/exit.d.ts +0 -1
- package/dist/types/lib/listen.d.ts +0 -5
- package/dist/types/lib/psql.d.ts +0 -5
- package/dist/types/lib/spawn-promise.d.ts +0 -1
- /package/{dist/cjs/index.d.ts → src/index.ts} +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# @onivoro/server-process
|
|
2
|
+
|
|
3
|
+
A lightweight Node.js process management library providing utilities for command execution, Docker container operations, PostgreSQL commands, and reactive process handling.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @onivoro/server-process
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
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
|
+
- **Line-by-Line Processing**: Stream processing for large outputs
|
|
20
|
+
|
|
21
|
+
## API Reference
|
|
22
|
+
|
|
23
|
+
### Command Execution
|
|
24
|
+
|
|
25
|
+
#### `execPromise(cmd, options?)`
|
|
26
|
+
|
|
27
|
+
Execute a command and return a promise that resolves to stdout as a string:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { execPromise } from '@onivoro/server-process';
|
|
31
|
+
|
|
32
|
+
// Simple command execution
|
|
33
|
+
const output = await execPromise('ls -la');
|
|
34
|
+
console.log(output); // stdout as string
|
|
35
|
+
|
|
36
|
+
// With options
|
|
37
|
+
const result = await execPromise('git status', { cwd: '/path/to/repo' });
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Parameters:**
|
|
41
|
+
- `cmd: string` - Command to execute
|
|
42
|
+
- `options?: EncodingOption & ExecOptions` - Node.js exec options
|
|
43
|
+
|
|
44
|
+
**Returns:** `Promise<string>` - Command stdout
|
|
45
|
+
|
|
46
|
+
#### `spawnPromise(program, args?, options?)`
|
|
47
|
+
|
|
48
|
+
Spawn a process and return a promise that resolves to joined stdout:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { spawnPromise } from '@onivoro/server-process';
|
|
52
|
+
|
|
53
|
+
// Spawn with arguments
|
|
54
|
+
const output = await spawnPromise('node', ['--version']);
|
|
55
|
+
console.log(output); // Node.js version
|
|
56
|
+
|
|
57
|
+
// With spawn options
|
|
58
|
+
const result = await spawnPromise('npm', ['install'], {
|
|
59
|
+
cwd: '/path/to/project',
|
|
60
|
+
env: { ...process.env, NODE_ENV: 'production' }
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
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)
|
|
70
|
+
|
|
71
|
+
### Reactive Command Execution
|
|
72
|
+
|
|
73
|
+
#### `execRx(cmd, options?, emitStdErr?)`
|
|
74
|
+
|
|
75
|
+
Execute a command reactively, emitting stdout (and optionally stderr):
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { execRx } from '@onivoro/server-process';
|
|
79
|
+
|
|
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')
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Without stderr
|
|
87
|
+
execRx('ls -la', undefined, false).subscribe(console.log);
|
|
88
|
+
```
|
|
89
|
+
|
|
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)
|
|
94
|
+
|
|
95
|
+
**Returns:** `Observable<string>` - Command output stream
|
|
96
|
+
|
|
97
|
+
#### `execRxAsLines(cmd, options?, emitStdErr?)`
|
|
98
|
+
|
|
99
|
+
Execute a command and emit output line by line:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { execRxAsLines } from '@onivoro/server-process';
|
|
103
|
+
|
|
104
|
+
execRxAsLines('cat large-file.txt').subscribe({
|
|
105
|
+
next: (line) => console.log('Line:', line),
|
|
106
|
+
complete: () => console.log('File processed')
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Parameters:**
|
|
111
|
+
- `cmd: string` - Command to execute
|
|
112
|
+
- `options?: ExecOptions` - Node.js exec options
|
|
113
|
+
- `emitStdErr?: boolean` - Include stderr in output (default: true)
|
|
114
|
+
|
|
115
|
+
**Returns:** `Observable<string>` - Stream of individual lines
|
|
116
|
+
|
|
117
|
+
#### `execRxAsJson(cmd, options?, emitStdErr?)`
|
|
118
|
+
|
|
119
|
+
Execute a command and parse output as JSON:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { execRxAsJson } from '@onivoro/server-process';
|
|
123
|
+
|
|
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
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Parameters:**
|
|
131
|
+
- `cmd: string` - Command to execute
|
|
132
|
+
- `options?: ExecOptions` - Node.js exec options
|
|
133
|
+
- `emitStdErr?: boolean` - Include stderr in output (default: true)
|
|
134
|
+
|
|
135
|
+
**Returns:** `Observable<any>` - Stream of parsed JSON objects
|
|
136
|
+
|
|
137
|
+
### Docker Class
|
|
138
|
+
|
|
139
|
+
Execute commands within Docker containers:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { Docker } from '@onivoro/server-process';
|
|
143
|
+
|
|
144
|
+
const docker = new Docker('my-postgres-container', 'psql');
|
|
145
|
+
|
|
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
|
+
```
|
|
152
|
+
|
|
153
|
+
**Constructor:**
|
|
154
|
+
- `containerName: string` - Name of the Docker container
|
|
155
|
+
- `binaryName: string` - Binary to execute within the container
|
|
156
|
+
|
|
157
|
+
**Methods:**
|
|
158
|
+
- `execRx(cmd, options?, emitStdErr?)` - Execute command reactively within container
|
|
159
|
+
|
|
160
|
+
### PSql Class
|
|
161
|
+
|
|
162
|
+
Execute PostgreSQL commands with optional Docker container support:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { PSql } from '@onivoro/server-process';
|
|
166
|
+
|
|
167
|
+
// Local psql
|
|
168
|
+
const psql = new PSql();
|
|
169
|
+
psql.execRx('SELECT COUNT(*) FROM users;', 'mydb', 'postgres').subscribe(console.log);
|
|
170
|
+
|
|
171
|
+
// Container-based psql
|
|
172
|
+
const containerPsql = new PSql('postgres-container');
|
|
173
|
+
containerPsql.execRx('SELECT NOW();', 'mydb', 'postgres').subscribe(console.log);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Constructor:**
|
|
177
|
+
- `containerName?: string` - Optional Docker container name
|
|
178
|
+
|
|
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
|
|
184
|
+
|
|
185
|
+
### Process I/O Management
|
|
186
|
+
|
|
187
|
+
#### `listen()`
|
|
188
|
+
|
|
189
|
+
Create reactive streams for process stdin/stdout:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { listen } from '@onivoro/server-process';
|
|
193
|
+
|
|
194
|
+
const { stdout, stdin } = listen();
|
|
195
|
+
|
|
196
|
+
// Handle stdin data
|
|
197
|
+
stdin.subscribe((data) => {
|
|
198
|
+
console.log('Received input:', data.toString());
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// stdin automatically completes when process.stdin closes
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Returns:** `{ stdout: Subject, stdin: Subject }` - Reactive streams for process I/O
|
|
205
|
+
|
|
206
|
+
#### `exit(code)`
|
|
207
|
+
|
|
208
|
+
Create a process exit function with specified exit code:
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { exit } from '@onivoro/server-process';
|
|
212
|
+
|
|
213
|
+
const exitWithError = exit(1);
|
|
214
|
+
const exitSuccess = exit(0);
|
|
215
|
+
|
|
216
|
+
// Use in error handling
|
|
217
|
+
if (someErrorCondition) {
|
|
218
|
+
console.error('Fatal error occurred');
|
|
219
|
+
exitWithError();
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Parameters:**
|
|
224
|
+
- `code: number` - Exit code
|
|
225
|
+
|
|
226
|
+
**Returns:** `() => never` - Function that exits the process
|
|
227
|
+
|
|
228
|
+
## Usage Examples
|
|
229
|
+
|
|
230
|
+
### Basic Command Execution
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import { execPromise, spawnPromise } from '@onivoro/server-process';
|
|
234
|
+
|
|
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
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Reactive Log Monitoring
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
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
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
monitorLogs('/var/log/app.log');
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Database Operations with Docker
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
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
|
+
}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### JSON Processing
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
import { execRxAsJson } from '@onivoro/server-process';
|
|
310
|
+
|
|
311
|
+
interface ContainerInfo {
|
|
312
|
+
Id: string;
|
|
313
|
+
Name: string;
|
|
314
|
+
State: { Status: string };
|
|
315
|
+
}
|
|
316
|
+
|
|
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
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Process I/O Handling
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
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
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Error Handling
|
|
358
|
+
|
|
359
|
+
All functions handle errors appropriately:
|
|
360
|
+
|
|
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
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
import { execPromise, execRx } from '@onivoro/server-process';
|
|
367
|
+
|
|
368
|
+
// Promise error handling
|
|
369
|
+
try {
|
|
370
|
+
await execPromise('nonexistent-command');
|
|
371
|
+
} catch (error) {
|
|
372
|
+
console.error('Command failed:', error.message);
|
|
373
|
+
}
|
|
374
|
+
|
|
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')
|
|
380
|
+
});
|
|
381
|
+
```
|
|
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
|
+
|
|
387
|
+
## License
|
|
388
|
+
|
|
389
|
+
This library is part of the Onivoro monorepo ecosystem.
|
package/jest.config.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
export default {
|
|
3
|
+
displayName: 'lib-server-process',
|
|
4
|
+
preset: '../../../jest.preset.js',
|
|
5
|
+
testEnvironment: 'node',
|
|
6
|
+
transform: {
|
|
7
|
+
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
|
|
8
|
+
},
|
|
9
|
+
moduleFileExtensions: ['ts', 'js', 'html'],
|
|
10
|
+
coverageDirectory: '../../../coverage/libs/server/process',
|
|
11
|
+
};
|
package/package.json
CHANGED
|
@@ -1,42 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
"files": [
|
|
11
|
-
"dist/*"
|
|
12
|
-
],
|
|
13
|
-
"scripts": {
|
|
14
|
-
"onx": "onx",
|
|
15
|
-
"build": "onx Build",
|
|
16
|
-
"deploy": "onx Publish",
|
|
17
|
-
"test": "onx Test",
|
|
18
|
-
"update": "onx Update"
|
|
19
|
-
},
|
|
20
|
-
"exports": {
|
|
21
|
-
".": {
|
|
22
|
-
"types": "./dist/types/index.d.ts",
|
|
23
|
-
"require": "./dist/cjs/index.js",
|
|
24
|
-
"import": "./dist/esm/index.js",
|
|
25
|
-
"default": "./dist/esm/lib.js"
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
"onx": {
|
|
29
|
-
"platform": "server",
|
|
30
|
-
"module": "commonjs"
|
|
31
|
-
},
|
|
32
|
-
"devDependencies": {
|
|
33
|
-
"@onivoro/cli": "^22.0.8",
|
|
34
|
-
"@types/jest": "*",
|
|
35
|
-
"@types/node": "^22.8.1",
|
|
36
|
-
"typescript": "*"
|
|
37
|
-
},
|
|
38
|
-
"engines": {
|
|
39
|
-
"node": "22.10.0",
|
|
40
|
-
"npm": "10.9.0"
|
|
41
|
-
}
|
|
2
|
+
"name": "@onivoro/server-process",
|
|
3
|
+
"version": "24.0.1",
|
|
4
|
+
"type": "commonjs",
|
|
5
|
+
"main": "./src/index.js",
|
|
6
|
+
"types": "./src/index.d.ts",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"tslib": "^2.3.0"
|
|
9
|
+
}
|
|
42
10
|
}
|
package/project.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lib-server-process",
|
|
3
|
+
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "libs/server/process/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"targets": {
|
|
7
|
+
"build": {
|
|
8
|
+
"executor": "@nx/js:tsc",
|
|
9
|
+
"outputs": ["{options.outputPath}"],
|
|
10
|
+
"options": {
|
|
11
|
+
"outputPath": "dist/libs/server/process",
|
|
12
|
+
"main": "libs/server/process/src/index.ts",
|
|
13
|
+
"tsConfig": "libs/server/process/tsconfig.lib.json",
|
|
14
|
+
"assets": [
|
|
15
|
+
"libs/server/process/README.md",
|
|
16
|
+
"libs/server/process/package.json"
|
|
17
|
+
],
|
|
18
|
+
"declaration": true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"tags": []
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ExecOptions } from "child_process";
|
|
2
|
+
import { EncodingOption } from "fs";
|
|
3
|
+
import { execRx } from "./exec-rx";
|
|
4
|
+
|
|
5
|
+
export class Docker {
|
|
6
|
+
constructor(
|
|
7
|
+
public readonly containerName: string,
|
|
8
|
+
public readonly binaryName: string,
|
|
9
|
+
) { }
|
|
10
|
+
|
|
11
|
+
execRx(cmd: string, options?: EncodingOption & ExecOptions, emitStdErr=true) {
|
|
12
|
+
return execRx(`docker exec ${this.containerName} ${this.binaryName} ${cmd}`, options, emitStdErr);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { parse } from 'path';
|
|
2
|
+
import { execPromise } from './exec-promise';
|
|
3
|
+
|
|
4
|
+
describe('execPromise', () => {
|
|
5
|
+
it('resolves with stdout', async () => {
|
|
6
|
+
const result = await execPromise(`ls ${__dirname}`);
|
|
7
|
+
expect(result).toContain(parse(__filename).base);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('rejects with stderr', async () => {
|
|
11
|
+
try {
|
|
12
|
+
await execPromise(`ls 'no way jose'`);
|
|
13
|
+
} catch (e) {
|
|
14
|
+
expect(e.message.replace(/\n/g, ' ')).toContain('No such file or directory');
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { exec, ExecOptions } from "child_process";
|
|
2
|
+
import { EncodingOption } from "fs";
|
|
3
|
+
|
|
4
|
+
export function execPromise(cmd: string, options?: EncodingOption & ExecOptions): Promise<any> {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
exec(cmd, options, (err, stdout) => {
|
|
7
|
+
if (err) {
|
|
8
|
+
reject(err);
|
|
9
|
+
} else {
|
|
10
|
+
resolve(stdout.toString());
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ExecOptions } from 'child_process';
|
|
2
|
+
import { map } from 'rxjs/operators';
|
|
3
|
+
import { execRx } from './exec-rx';
|
|
4
|
+
|
|
5
|
+
export const execRxAsJson = (cmd: string, options?: ExecOptions, emitStdErr = true) => {
|
|
6
|
+
return execRx(cmd, options, emitStdErr).pipe(
|
|
7
|
+
map((s: string) => JSON.parse(s)),
|
|
8
|
+
)
|
|
9
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ExecOptions } from 'child_process';
|
|
2
|
+
import { from } from 'rxjs';
|
|
3
|
+
import { concatMap } from 'rxjs/operators';
|
|
4
|
+
import { execRx } from './exec-rx';
|
|
5
|
+
|
|
6
|
+
export const execRxAsLines = (cmd: string, options?: ExecOptions, emitStdErr = true) => {
|
|
7
|
+
return execRx(cmd, options, emitStdErr).pipe(
|
|
8
|
+
concatMap((s: string) => from(s.split('\n'))),
|
|
9
|
+
)
|
|
10
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { execRx } from './exec-rx';
|
|
2
|
+
import { of } from 'rxjs';
|
|
3
|
+
import { catchError } from 'rxjs/operators';
|
|
4
|
+
|
|
5
|
+
describe(execRx.name, () => {
|
|
6
|
+
describe('GIVEN command succeeds', () => {
|
|
7
|
+
it('returns the stdout', (done) => {
|
|
8
|
+
execRx(`cat ${__filename}`).subscribe((d) => {
|
|
9
|
+
expect(d).toEqual(expect.stringContaining('execRx worx!'));
|
|
10
|
+
done();
|
|
11
|
+
}, () => { throw new Error("fail") }
|
|
12
|
+
);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('GIVEN command fails', () => {
|
|
17
|
+
it('emits error', (done) => {
|
|
18
|
+
execRx(`cat ${__filename + 'blah'}`).pipe(catchError(() => of(done())))
|
|
19
|
+
.subscribe();
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { exec, ExecOptions } from "child_process";
|
|
2
|
+
import { EncodingOption } from "fs";
|
|
3
|
+
import { Observable } from "rxjs";
|
|
4
|
+
|
|
5
|
+
export function execRx(cmd: string, options?: EncodingOption & ExecOptions, emitStdErr=true): Observable<any> {
|
|
6
|
+
return new Observable((observer) => {
|
|
7
|
+
exec(cmd, options, (err, stdout, stderr) => {
|
|
8
|
+
if (err) {
|
|
9
|
+
observer.error(err);
|
|
10
|
+
} else {
|
|
11
|
+
observer.next(`${stdout}${emitStdErr && ` ${stderr}`}`);
|
|
12
|
+
observer.complete();
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
package/src/lib/exit.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const exit = (code: number) => process.exit.bind(process, code);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Subject } from 'rxjs';
|
|
2
|
+
|
|
3
|
+
const stdin = new Subject();
|
|
4
|
+
const stdout = new Subject();
|
|
5
|
+
|
|
6
|
+
export const listen = () => {
|
|
7
|
+
|
|
8
|
+
process.stdin.on('data', d => stdin.next(d));
|
|
9
|
+
process.stdin.on('close', () => stdin.complete());
|
|
10
|
+
|
|
11
|
+
return { stdout, stdin };
|
|
12
|
+
}
|
package/src/lib/psql.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Docker } from "./docker";
|
|
2
|
+
import { execRx } from "./exec-rx";
|
|
3
|
+
|
|
4
|
+
const binaryName = 'psql';
|
|
5
|
+
|
|
6
|
+
export class PSql {
|
|
7
|
+
constructor(public readonly containerName: string = '') { }
|
|
8
|
+
|
|
9
|
+
execRx(cmd: string, db: string, username: string) {
|
|
10
|
+
const dbOptions = db ? ['-d', db] : [];
|
|
11
|
+
const commonArgs = ['-qtAX', '-U', username, ...dbOptions, '-c'].join(' ') + cmd;
|
|
12
|
+
return this.containerName
|
|
13
|
+
? (new Docker(this.containerName, binaryName)).execRx(commonArgs)
|
|
14
|
+
: execRx([binaryName, commonArgs].join(' '));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { parse } from 'path';
|
|
2
|
+
import { spawnPromise } from './spawn-promise';
|
|
3
|
+
|
|
4
|
+
describe('spawnPromise', () => {
|
|
5
|
+
it('resolves with stdout', async () => {
|
|
6
|
+
const result = await spawnPromise(`ls`, [__dirname]);
|
|
7
|
+
expect(result).toContain(parse(__filename).base);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('rejects with stderr', async () => {
|
|
11
|
+
try {
|
|
12
|
+
await spawnPromise(`ls`, ['no way jose']);
|
|
13
|
+
} catch (e) {
|
|
14
|
+
expect(e).toContain('No such file or directory');
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
|
|
3
|
+
const data = 'data';
|
|
4
|
+
|
|
5
|
+
export function spawnPromise(program: string, args?: string[], options?: any) {
|
|
6
|
+
return new Promise((res, rej) => {
|
|
7
|
+
let stdout: string[] = [];
|
|
8
|
+
let stderr: string[] = [];
|
|
9
|
+
|
|
10
|
+
const proc = spawn(program, args, options);
|
|
11
|
+
|
|
12
|
+
proc.stdout.on(data, (data) => {
|
|
13
|
+
stdout.push(`${data}`);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
proc.stderr.on(data, (data) => {
|
|
17
|
+
stderr.push(`${data}`);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
proc.on('close', (code) => {
|
|
21
|
+
if (code) {
|
|
22
|
+
rej(stderr.join(' '));
|
|
23
|
+
} else {
|
|
24
|
+
res(stdout.join(' '));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
stdout = undefined as any;
|
|
28
|
+
stderr = undefined as any;
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../tsconfig.server.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../../../dist/out-tsc"
|
|
5
|
+
},
|
|
6
|
+
"files": [],
|
|
7
|
+
"include": [],
|
|
8
|
+
"references": [
|
|
9
|
+
{
|
|
10
|
+
"path": "./tsconfig.lib.json"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"path": "./tsconfig.spec.json"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|