@onivoro/server-process 22.0.1 → 24.0.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.
Files changed (69) hide show
  1. package/README.md +526 -0
  2. package/jest.config.ts +11 -0
  3. package/package.json +8 -40
  4. package/project.json +23 -0
  5. package/src/lib/docker.ts +14 -0
  6. package/src/lib/exec-promise.spec.ts +17 -0
  7. package/src/lib/exec-promise.ts +14 -0
  8. package/src/lib/exec-rx-as-json.ts +9 -0
  9. package/src/lib/exec-rx-as-lines.ts +10 -0
  10. package/src/lib/exec-rx.spec.ts +22 -0
  11. package/src/lib/exec-rx.ts +16 -0
  12. package/src/lib/exit.ts +1 -0
  13. package/src/lib/listen.ts +12 -0
  14. package/src/lib/psql.ts +16 -0
  15. package/src/lib/spawn-promise.spec.ts +17 -0
  16. package/src/lib/spawn-promise.ts +31 -0
  17. package/tsconfig.json +16 -0
  18. package/tsconfig.lib.json +8 -0
  19. package/tsconfig.spec.json +21 -0
  20. package/dist/cjs/index.js +0 -21
  21. package/dist/cjs/lib/docker.d.ts +0 -8
  22. package/dist/cjs/lib/docker.js +0 -16
  23. package/dist/cjs/lib/exec-promise.d.ts +0 -3
  24. package/dist/cjs/lib/exec-promise.js +0 -16
  25. package/dist/cjs/lib/exec-rx-as-json.d.ts +0 -2
  26. package/dist/cjs/lib/exec-rx-as-json.js +0 -9
  27. package/dist/cjs/lib/exec-rx-as-lines.d.ts +0 -2
  28. package/dist/cjs/lib/exec-rx-as-lines.js +0 -10
  29. package/dist/cjs/lib/exec-rx.d.ts +0 -4
  30. package/dist/cjs/lib/exec-rx.js +0 -18
  31. package/dist/cjs/lib/exit.d.ts +0 -1
  32. package/dist/cjs/lib/exit.js +0 -5
  33. package/dist/cjs/lib/listen.d.ts +0 -5
  34. package/dist/cjs/lib/listen.js +0 -12
  35. package/dist/cjs/lib/psql.d.ts +0 -5
  36. package/dist/cjs/lib/psql.js +0 -20
  37. package/dist/cjs/lib/spawn-promise.d.ts +0 -1
  38. package/dist/cjs/lib/spawn-promise.js +0 -28
  39. package/dist/esm/index.d.ts +0 -9
  40. package/dist/esm/index.js +0 -21
  41. package/dist/esm/lib/docker.d.ts +0 -8
  42. package/dist/esm/lib/docker.js +0 -16
  43. package/dist/esm/lib/exec-promise.d.ts +0 -3
  44. package/dist/esm/lib/exec-promise.js +0 -16
  45. package/dist/esm/lib/exec-rx-as-json.d.ts +0 -2
  46. package/dist/esm/lib/exec-rx-as-json.js +0 -9
  47. package/dist/esm/lib/exec-rx-as-lines.d.ts +0 -2
  48. package/dist/esm/lib/exec-rx-as-lines.js +0 -10
  49. package/dist/esm/lib/exec-rx.d.ts +0 -4
  50. package/dist/esm/lib/exec-rx.js +0 -18
  51. package/dist/esm/lib/exit.d.ts +0 -1
  52. package/dist/esm/lib/exit.js +0 -5
  53. package/dist/esm/lib/listen.d.ts +0 -5
  54. package/dist/esm/lib/listen.js +0 -12
  55. package/dist/esm/lib/psql.d.ts +0 -5
  56. package/dist/esm/lib/psql.js +0 -20
  57. package/dist/esm/lib/spawn-promise.d.ts +0 -1
  58. package/dist/esm/lib/spawn-promise.js +0 -28
  59. package/dist/types/index.d.ts +0 -9
  60. package/dist/types/lib/docker.d.ts +0 -8
  61. package/dist/types/lib/exec-promise.d.ts +0 -3
  62. package/dist/types/lib/exec-rx-as-json.d.ts +0 -2
  63. package/dist/types/lib/exec-rx-as-lines.d.ts +0 -2
  64. package/dist/types/lib/exec-rx.d.ts +0 -4
  65. package/dist/types/lib/exit.d.ts +0 -1
  66. package/dist/types/lib/listen.d.ts +0 -5
  67. package/dist/types/lib/psql.d.ts +0 -5
  68. package/dist/types/lib/spawn-promise.d.ts +0 -1
  69. /package/{dist/cjs/index.d.ts → src/index.ts} +0 -0
package/README.md ADDED
@@ -0,0 +1,526 @@
1
+ # @onivoro/server-process
2
+
3
+ A comprehensive process management library for Node.js applications, providing utilities for executing commands, managing Docker containers, PostgreSQL operations, and handling system processes with reactive programming support.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @onivoro/server-process
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **Command Execution**: Promise-based and reactive command execution
14
+ - **Docker Integration**: Docker container management utilities
15
+ - **PostgreSQL Tools**: PSql command execution and database operations
16
+ - **Process Management**: Spawn and manage child processes
17
+ - **Reactive Streams**: RxJS-based reactive process handling
18
+ - **JSON Processing**: Parse command output as JSON
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
+
23
+ ## Quick Start
24
+
25
+ ### Basic Command Execution
26
+
27
+ ```typescript
28
+ import { execPromise, shell } from '@onivoro/server-process';
29
+
30
+ // Simple command execution
31
+ const result = await execPromise('ls -la');
32
+ console.log(result.stdout);
33
+
34
+ // Alternative using shell function (if available in common)
35
+ const output = await shell('pwd');
36
+ console.log(output);
37
+ ```
38
+
39
+ ### Docker Operations
40
+
41
+ ```typescript
42
+ import { Docker } from '@onivoro/server-process';
43
+
44
+ const docker = new Docker();
45
+
46
+ // List running containers
47
+ const containers = await docker.listContainers();
48
+
49
+ // Run a container
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
+ ```
59
+
60
+ ### PostgreSQL Operations
61
+
62
+ ```typescript
63
+ import { PSql } from '@onivoro/server-process';
64
+
65
+ const psql = new PSql({
66
+ host: 'localhost',
67
+ port: 5432,
68
+ database: 'mydb',
69
+ username: 'user',
70
+ password: 'password'
71
+ });
72
+
73
+ // Execute SQL query
74
+ const users = await psql.query('SELECT * FROM users');
75
+
76
+ // Execute SQL file
77
+ await psql.executeFile('./migrations/001_create_tables.sql');
78
+ ```
79
+
80
+ ## Usage Examples
81
+
82
+ ### Reactive Command Execution
83
+
84
+ ```typescript
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
+ ```
114
+
115
+ ### Process Spawning
116
+
117
+ ```typescript
118
+ import { spawnPromise } from '@onivoro/server-process';
119
+
120
+ // Spawn a long-running process
121
+ const result = await spawnPromise('node', ['server.js'], {
122
+ cwd: '/path/to/app',
123
+ env: { ...process.env, NODE_ENV: 'production' }
124
+ });
125
+
126
+ console.log('Process exit code:', result.code);
127
+ console.log('Process output:', result.stdout);
128
+ ```
129
+
130
+ ### Docker Container Management
131
+
132
+ ```typescript
133
+ import { Docker } from '@onivoro/server-process';
134
+
135
+ class ContainerManager {
136
+ private docker = new Docker();
137
+
138
+ async deployApplication(imageName: string, config: any): Promise<string> {
139
+ // Pull latest image
140
+ await this.docker.pull(imageName);
141
+
142
+ // Stop existing container if running
143
+ try {
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
+ }
166
+
167
+ async healthCheck(containerName: string): Promise<boolean> {
168
+ try {
169
+ const result = await this.docker.exec(containerName, 'curl -f http://localhost/health');
170
+ return result.exitCode === 0;
171
+ } catch (error) {
172
+ return false;
173
+ }
174
+ }
175
+ }
176
+ ```
177
+
178
+ ### Database Migration with PSql
179
+
180
+ ```typescript
181
+ import { PSql } from '@onivoro/server-process';
182
+ import { readdir } from 'fs/promises';
183
+ import { join } from 'path';
184
+
185
+ class MigrationRunner {
186
+ private psql: PSql;
187
+
188
+ constructor(connectionConfig: any) {
189
+ this.psql = new PSql(connectionConfig);
190
+ }
191
+
192
+ async runMigrations(migrationsDir: string): Promise<void> {
193
+ // Ensure migrations table exists
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
+ }
233
+
234
+ async rollback(steps: number = 1): Promise<void> {
235
+ const migrations = await this.psql.query(
236
+ 'SELECT filename FROM migrations ORDER BY id DESC LIMIT $1',
237
+ [steps]
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
+ }
257
+ ```
258
+
259
+ ### System Monitoring
260
+
261
+ ```typescript
262
+ import { execPromise, execRxAsLines } from '@onivoro/server-process';
263
+
264
+ class SystemMonitor {
265
+ async getSystemInfo(): Promise<any> {
266
+ const [cpu, memory, disk] = await Promise.all([
267
+ this.getCpuInfo(),
268
+ this.getMemoryInfo(),
269
+ this.getDiskInfo()
270
+ ]);
271
+
272
+ return { cpu, memory, disk };
273
+ }
274
+
275
+ private async getCpuInfo(): Promise<any> {
276
+ const result = await execPromise('cat /proc/cpuinfo');
277
+ // Parse CPU information
278
+ return this.parseCpuInfo(result.stdout);
279
+ }
280
+
281
+ private async getMemoryInfo(): Promise<any> {
282
+ const result = await execPromise('free -m');
283
+ return this.parseMemoryInfo(result.stdout);
284
+ }
285
+
286
+ private async getDiskInfo(): Promise<any> {
287
+ const result = await execPromise('df -h');
288
+ return this.parseDiskInfo(result.stdout);
289
+ }
290
+
291
+ monitorSystemLogs(): void {
292
+ execRxAsLines('tail -f /var/log/syslog')
293
+ .subscribe(line => {
294
+ if (this.isErrorLog(line)) {
295
+ this.handleSystemError(line);
296
+ }
297
+ });
298
+ }
299
+
300
+ private isErrorLog(line: string): boolean {
301
+ return line.toLowerCase().includes('error') ||
302
+ line.toLowerCase().includes('fail');
303
+ }
304
+
305
+ private handleSystemError(errorLine: string): void {
306
+ console.error('System error detected:', errorLine);
307
+ // Implement alerting logic
308
+ }
309
+ }
310
+ ```
311
+
312
+ ### Process Exit Handling
313
+
314
+ ```typescript
315
+ import { exit, listen } from '@onivoro/server-process';
316
+
317
+ class GracefulShutdown {
318
+ private cleanup: Array<() => Promise<void>> = [];
319
+
320
+ addCleanupTask(task: () => Promise<void>): void {
321
+ this.cleanup.push(task);
322
+ }
323
+
324
+ setupGracefulShutdown(): void {
325
+ // Listen for termination signals
326
+ listen('SIGTERM', async () => {
327
+ console.log('Received SIGTERM, shutting down gracefully...');
328
+ await this.performCleanup();
329
+ exit(0);
330
+ });
331
+
332
+ listen('SIGINT', async () => {
333
+ console.log('Received SIGINT, shutting down gracefully...');
334
+ await this.performCleanup();
335
+ exit(0);
336
+ });
337
+
338
+ // Handle uncaught exceptions
339
+ process.on('uncaughtException', async (error) => {
340
+ console.error('Uncaught exception:', error);
341
+ await this.performCleanup();
342
+ exit(1);
343
+ });
344
+ }
345
+
346
+ private async performCleanup(): Promise<void> {
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
+ }
361
+
362
+ // Usage
363
+ const shutdown = new GracefulShutdown();
364
+
365
+ // Add cleanup tasks
366
+ shutdown.addCleanupTask(async () => {
367
+ // Close database connections
368
+ await database.close();
369
+ });
370
+
371
+ shutdown.addCleanupTask(async () => {
372
+ // Stop background jobs
373
+ await jobQueue.stop();
374
+ });
375
+
376
+ shutdown.setupGracefulShutdown();
377
+ ```
378
+
379
+ ## API Reference
380
+
381
+ ### Command Execution
382
+
383
+ #### execPromise(command, options?)
384
+
385
+ Execute a command and return a promise:
386
+
387
+ ```typescript
388
+ interface ExecResult {
389
+ stdout: string;
390
+ stderr: string;
391
+ code: number;
392
+ }
393
+
394
+ function execPromise(command: string, options?: ExecOptions): Promise<ExecResult>
395
+ ```
396
+
397
+ #### spawnPromise(command, args?, options?)
398
+
399
+ Spawn a process and return a promise:
400
+
401
+ ```typescript
402
+ function spawnPromise(
403
+ command: string,
404
+ args?: string[],
405
+ options?: SpawnOptions
406
+ ): Promise<SpawnResult>
407
+ ```
408
+
409
+ ### Reactive Execution
410
+
411
+ #### execRx(command, options?)
412
+
413
+ Execute command reactively:
414
+
415
+ ```typescript
416
+ function execRx(command: string, options?: ExecOptions): Observable<string>
417
+ ```
418
+
419
+ #### execRxAsLines(command, options?)
420
+
421
+ Execute command and emit output line by line:
422
+
423
+ ```typescript
424
+ function execRxAsLines(command: string, options?: ExecOptions): Observable<string>
425
+ ```
426
+
427
+ #### execRxAsJson(command, options?)
428
+
429
+ Execute command and parse output as JSON:
430
+
431
+ ```typescript
432
+ function execRxAsJson<T>(command: string, options?: ExecOptions): Observable<T>
433
+ ```
434
+
435
+ ### Docker Class
436
+
437
+ Docker container management:
438
+
439
+ ```typescript
440
+ class Docker {
441
+ async run(image: string, options?: DockerRunOptions): Promise<string>
442
+ async stop(container: string): Promise<void>
443
+ async remove(container: string): Promise<void>
444
+ async exec(container: string, command: string): Promise<ExecResult>
445
+ async logs(container: string, options?: LogOptions): Promise<string>
446
+ async listContainers(): Promise<ContainerInfo[]>
447
+ async pull(image: string): Promise<void>
448
+ }
449
+ ```
450
+
451
+ ### PSql Class
452
+
453
+ PostgreSQL operations:
454
+
455
+ ```typescript
456
+ class PSql {
457
+ constructor(config: PSqlConfig)
458
+ async query(sql: string, params?: any[]): Promise<any[]>
459
+ async executeFile(filePath: string): Promise<void>
460
+ async transaction<T>(callback: (client: any) => Promise<T>): Promise<T>
461
+ }
462
+ ```
463
+
464
+ ### Process Management
465
+
466
+ #### listen(signal, callback)
467
+
468
+ Listen for process signals:
469
+
470
+ ```typescript
471
+ function listen(signal: string, callback: () => void | Promise<void>): void
472
+ ```
473
+
474
+ #### exit(code?)
475
+
476
+ Exit process with optional code:
477
+
478
+ ```typescript
479
+ function exit(code?: number): never
480
+ ```
481
+
482
+ ## Best Practices
483
+
484
+ 1. **Error Handling**: Always handle errors in process operations
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
492
+
493
+ ## Security Considerations
494
+
495
+ 1. **Command Injection**: Always validate and sanitize command inputs
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
502
+
503
+ ```typescript
504
+ import { execPromise, Docker } from '@onivoro/server-process';
505
+
506
+ describe('Process Operations', () => {
507
+ it('should execute commands', async () => {
508
+ const result = await execPromise('echo "Hello World"');
509
+ expect(result.stdout.trim()).toBe('Hello World');
510
+ expect(result.code).toBe(0);
511
+ });
512
+
513
+ it('should handle command errors', async () => {
514
+ try {
515
+ await execPromise('nonexistent-command');
516
+ fail('Should have thrown an error');
517
+ } catch (error) {
518
+ expect(error.code).not.toBe(0);
519
+ }
520
+ });
521
+ });
522
+ ```
523
+
524
+ ## License
525
+
526
+ 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
- "name": "@onivoro/server-process",
3
- "version": "22.0.1",
4
- "repository": {
5
- "url": "git+https://github.com/onivoro/server-process.git"
6
- },
7
- "main": "dist/cjs/index.js",
8
- "module": "dist/esm/index.js",
9
- "types": "dist/types/index.d.ts",
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.4",
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.0",
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
+ });