1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 |
x2
x2
x2
x2
x2
x9
x3
x2
x9
x3
|
|
import type { Fn, Step, Async, Ports } from '../index.ts';
import type { ChildProcess } from '../deps.ts';
import { execImpl, spawnImpl, inspect, getCwd } from '../deps.ts';
import { Error, Pipe, Name, toString } from '../format.ts';
import { Dir } from './fs.ts';
/** A collection of processes and network endpoints provided by them. */
export type Service = Dir & Ports<number> & {
/** Processes comprising the service, by PID. */
pids: Record<number, ChildProcess>;
/** Terminate the service. */
kill: Fn<[], Async>;
};
/** Command invocation that returns a result. */
export type Exec = Fn<[Partial<Dir & { exec?: typeof execImpl }>],
Async<Run & {
pid?: number,
status?: number|null,
signal?: string|null,
error?: Error,
stdout?: string|unknown,
stderr?: string|unknown,
}>>;
/** Run a command and wait for result. */
export function Exec (
command: string, ...options: (Step<Run>|string)[]
): Exec {
return Name(`Exec(${command})`, async function exec (context?: {
dir?: string, exec?: typeof execImpl,
}): Promise<Run> {
context ??= {};
context.dir ??= getCwd();
context.exec ??= execImpl;
const { argv, env } = Run(command, ...options);
const [ cmd, ...args ] = argv;
const opts = { env, cwd: context?.dir ?? getCwd() }
return { argv, env, ...await context.exec(cmd, args, opts) };
}, { command, options });
}
/** Command invocation that spawns a background process. */
export type Spawn = Fn<[Partial<Dir & { spawn?: typeof spawnImpl }>],
Async<Run & ChildProcess>>;
/** Run a background service. */
export function Spawn (daemon: string, ...options: (Step<Run>|string)[]): Spawn {
return Name(`Spawn(${daemon})`, async function spawn (context?: {
dir?: string, spawn?: typeof spawnImpl
}): Promise<Run & ChildProcess> {
context ??= {};
context.dir ??= getCwd();
context.spawn ??= spawnImpl;
const { argv, env } = Run(daemon, ...options);
const [ cmd, ...args ] = argv;
const opts = { env, cwd: context?.dir ?? getCwd() }
return { argv, env, ...await context.spawn(cmd, args, opts) } as Run & ChildProcess;
}, { daemon, options });
}
/** Command invocation. */
export type Run = { argv: string[], env?: Record<string, string> };
/** Define a service. */
export function Service <S extends Service> (
name: string, ...services: Fn<[S]>[]
) {
const info = `[Service (${services.length}): ${name}]`;
return toString(info)(Name(name, runService, { services }));
async function runService (
ctx = { ...Dir(), pids: {}, ports: {} } as Partial<S>
): Promise<S> {
for (const service of services) await service(ctx);
const kill = () => Promise.all(Object.values(ctx.pids).map(proc=>proc.kill()));
return Object.assign(ctx, { kill, }) as unknown as S;
}
}
/** Set environment variable in run config. */
export function Env (name: string, value: string|null) {
return Name(`Env(${name}=${value})`, function setEnv (context: Run) {
context ??= {} as Run;
context.env ??= {};
context.env[name] = value;
return context
}, { name, value });
}
/** Append command-line arguments to a command invocation. */
export function Arg (...parts: string[]) {
return Name(`Arg(${parts[0]})`, function addArgument (context: Run) {
context ??= {} as Run;
context.argv ??= []
context.argv.push(parts.join(' '))
return context
}, { parts });
}
/** Compose command invocation from options. */
export function Run (path: string, ...opts: (Step<Run>|string)[]) {
return Pipe(...opts.filter(Boolean).map(toOpt))({ argv: [path], env: {} }) as Run;
}
const toOpt = (opt: string|Step<Run>): Step<Run> =>
(typeof opt === 'function') ? opt :
(typeof opt === 'string') ? pushArg(opt) :
(typeof opt === 'object') ? context=>Object.assign(context, opt) :
Error.required(`string or function, got: ${inspect(opt)}`);
const pushArg = (opt: string) =>
Name(opt, (run: Run) => { run.argv.push(opt); return run });
|