Skip to content

Commit bc38276

Browse files
committed
add exec helper to core
1 parent a254602 commit bc38276

File tree

4 files changed

+119
-7
lines changed

4 files changed

+119
-7
lines changed

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@
205205
"nanoid": "^3.3.4",
206206
"socket.io-client": "4.7.5",
207207
"superjson": "^2.2.1",
208+
"tinyexec": "^0.3.2",
208209
"zod": "3.23.8",
209210
"zod-error": "1.5.0",
210211
"zod-validation-error": "^1.5.0"

packages/core/src/v3/apps/exec.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { SimpleStructuredLogger } from "../utils/structuredLogger.js";
2+
import { type Result, x } from "tinyexec";
3+
4+
export class ExecResult {
5+
pid?: number;
6+
exitCode?: number;
7+
aborted: boolean;
8+
killed: boolean;
9+
10+
constructor(result: Result) {
11+
this.pid = result.pid;
12+
this.exitCode = result.exitCode;
13+
this.aborted = result.aborted;
14+
this.killed = result.killed;
15+
}
16+
}
17+
18+
export interface ExecOptions {
19+
logger?: SimpleStructuredLogger;
20+
abortSignal?: AbortSignal;
21+
logOutput?: boolean;
22+
trimArgs?: boolean;
23+
neverThrow?: boolean;
24+
}
25+
26+
export class Exec {
27+
private logger: SimpleStructuredLogger;
28+
private abortSignal: AbortSignal | undefined;
29+
30+
private logOutput: boolean;
31+
private trimArgs: boolean;
32+
private neverThrow: boolean;
33+
34+
constructor(opts: ExecOptions) {
35+
this.logger = opts.logger ?? new SimpleStructuredLogger("exec");
36+
this.abortSignal = opts.abortSignal;
37+
38+
this.logOutput = opts.logOutput ?? true;
39+
this.trimArgs = opts.trimArgs ?? true;
40+
this.neverThrow = opts.neverThrow ?? false;
41+
}
42+
43+
async x(
44+
command: string,
45+
args?: string[],
46+
opts?: { neverThrow?: boolean; ignoreAbort?: boolean }
47+
) {
48+
const argsTrimmed = this.trimArgs ? args?.map((arg) => arg.trim()) : args;
49+
50+
const commandWithFirstArg = `${command}${argsTrimmed?.length ? ` ${argsTrimmed[0]}` : ""}`;
51+
this.logger.debug(`exec: ${commandWithFirstArg}`, { command, args, argsTrimmed });
52+
53+
const result = x(command, argsTrimmed, {
54+
signal: opts?.ignoreAbort ? undefined : this.abortSignal,
55+
// We don't use this as it doesn't cover killed and aborted processes
56+
// throwOnError: true,
57+
});
58+
59+
const output = await result;
60+
61+
const metadata = {
62+
command,
63+
argsRaw: args,
64+
argsTrimmed,
65+
globalOpts: {
66+
trimArgs: this.trimArgs,
67+
neverThrow: this.neverThrow,
68+
hasAbortSignal: !!this.abortSignal,
69+
},
70+
localOpts: opts,
71+
stdout: output.stdout,
72+
stderr: output.stderr,
73+
pid: result.pid,
74+
exitCode: result.exitCode,
75+
aborted: result.aborted,
76+
killed: result.killed,
77+
};
78+
79+
if (this.logOutput) {
80+
this.logger.debug(`output: ${commandWithFirstArg}`, metadata);
81+
}
82+
83+
if (this.neverThrow || opts?.neverThrow) {
84+
return output;
85+
}
86+
87+
if (result.aborted) {
88+
this.logger.error(`aborted: ${commandWithFirstArg}`, metadata);
89+
throw new ExecResult(result);
90+
}
91+
92+
if (result.killed) {
93+
this.logger.error(`killed: ${commandWithFirstArg}`, metadata);
94+
throw new ExecResult(result);
95+
}
96+
97+
if (result.exitCode !== 0) {
98+
this.logger.error(`non-zero exit: ${commandWithFirstArg}`, metadata);
99+
throw new ExecResult(result);
100+
}
101+
102+
return output;
103+
}
104+
105+
static Result = ExecResult;
106+
}

packages/core/src/v3/apps/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export * from "./duration.js";
1010
export * from "./maxDuration.js";
1111
export * from "./queueName.js";
1212
export * from "./consts.js";
13+
export * from "./exec.js";

pnpm-lock.yaml

Lines changed: 11 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)