@trim21/personal-pi-extensions 0.0.134 → 0.0.135
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/package.json +1 -1
- package/src/gh-readonly.ts +44 -14
package/package.json
CHANGED
package/src/gh-readonly.ts
CHANGED
|
@@ -43,11 +43,13 @@ interface GhResult {
|
|
|
43
43
|
code: number;
|
|
44
44
|
killed: boolean;
|
|
45
45
|
combined: string;
|
|
46
|
+
/** Why the process was killed, when `killed` is true. */
|
|
47
|
+
reason?: "timeout" | "abort";
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
49
51
|
|
|
50
|
-
function runGh(
|
|
52
|
+
export function runGh(
|
|
51
53
|
args: string[],
|
|
52
54
|
ctx: { cwd?: string; signal?: AbortSignal; timeout?: number },
|
|
53
55
|
): Promise<GhResult> {
|
|
@@ -63,11 +65,14 @@ function runGh(
|
|
|
63
65
|
let stderr = "";
|
|
64
66
|
const combined: string[] = [];
|
|
65
67
|
let killed = false;
|
|
68
|
+
let killReason: "timeout" | "abort" | undefined;
|
|
66
69
|
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
70
|
+
let onAbort: (() => void) | undefined;
|
|
67
71
|
|
|
68
|
-
const killProcess = () => {
|
|
72
|
+
const killProcess = (reason: "timeout" | "abort") => {
|
|
69
73
|
if (!killed) {
|
|
70
74
|
killed = true;
|
|
75
|
+
killReason = reason;
|
|
71
76
|
proc.kill("SIGTERM");
|
|
72
77
|
setTimeout(() => {
|
|
73
78
|
if (!proc.killed) proc.kill("SIGKILL");
|
|
@@ -76,16 +81,21 @@ function runGh(
|
|
|
76
81
|
};
|
|
77
82
|
|
|
78
83
|
if (ctx.signal) {
|
|
84
|
+
onAbort = () => killProcess("abort");
|
|
79
85
|
if (ctx.signal.aborted) {
|
|
80
|
-
killProcess();
|
|
86
|
+
killProcess("abort");
|
|
81
87
|
} else {
|
|
82
|
-
ctx.signal.addEventListener("abort",
|
|
88
|
+
ctx.signal.addEventListener("abort", onAbort, { once: true });
|
|
83
89
|
}
|
|
84
90
|
}
|
|
85
91
|
|
|
86
|
-
|
|
92
|
+
// Default timeout: 10 minutes. Long operations like downloading a CI job's
|
|
93
|
+
// full log routinely take well over 30s, so a short default would kill them
|
|
94
|
+
// mid-transfer; combined with `code ?? 0` that would silently cache a
|
|
95
|
+
// truncated log as success. A killed process must never look successful.
|
|
96
|
+
const timeout = ctx.timeout ?? 600_000;
|
|
87
97
|
if (timeout > 0) {
|
|
88
|
-
timeoutId = setTimeout(killProcess, timeout);
|
|
98
|
+
timeoutId = setTimeout(() => killProcess("timeout"), timeout);
|
|
89
99
|
}
|
|
90
100
|
|
|
91
101
|
proc.stdout?.on("data", (data: Buffer) => {
|
|
@@ -101,18 +111,29 @@ function runGh(
|
|
|
101
111
|
|
|
102
112
|
proc.on("close", (code) => {
|
|
103
113
|
if (timeoutId) clearTimeout(timeoutId);
|
|
104
|
-
if (ctx.signal) {
|
|
105
|
-
ctx.signal.removeEventListener("abort",
|
|
114
|
+
if (ctx.signal && onAbort) {
|
|
115
|
+
ctx.signal.removeEventListener("abort", onAbort);
|
|
106
116
|
}
|
|
107
|
-
resolve({
|
|
117
|
+
resolve({
|
|
118
|
+
stdout,
|
|
119
|
+
stderr,
|
|
120
|
+
// When killed by a signal the close event's code is null; report the
|
|
121
|
+
// process as failed instead of pretending it succeeded. -1 is a
|
|
122
|
+
// sentinel for "did not exit normally" — distinct from a real gh
|
|
123
|
+
// failure exit code (1), which is always in 0-255.
|
|
124
|
+
code: code ?? (killed ? -1 : 0),
|
|
125
|
+
killed,
|
|
126
|
+
combined: combined.join(""),
|
|
127
|
+
reason: killReason,
|
|
128
|
+
});
|
|
108
129
|
});
|
|
109
130
|
|
|
110
131
|
proc.on("error", () => {
|
|
111
132
|
if (timeoutId) clearTimeout(timeoutId);
|
|
112
|
-
if (ctx.signal) {
|
|
113
|
-
ctx.signal.removeEventListener("abort",
|
|
133
|
+
if (ctx.signal && onAbort) {
|
|
134
|
+
ctx.signal.removeEventListener("abort", onAbort);
|
|
114
135
|
}
|
|
115
|
-
resolve({ stdout, stderr, code: 1, killed, combined: combined.join("") });
|
|
136
|
+
resolve({ stdout, stderr, code: 1, killed, combined: combined.join(""), reason: killReason });
|
|
116
137
|
});
|
|
117
138
|
});
|
|
118
139
|
}
|
|
@@ -131,7 +152,16 @@ export class GhError extends Error {
|
|
|
131
152
|
|
|
132
153
|
constructor(args: string[], result: GhResult, input?: unknown) {
|
|
133
154
|
const inputText = input === undefined ? "" : `<input>${JSON.stringify(input)}<input>\n`;
|
|
134
|
-
|
|
155
|
+
const killedText = result.killed
|
|
156
|
+
? result.reason === "timeout"
|
|
157
|
+
? " (command timed out)"
|
|
158
|
+
: result.reason === "abort"
|
|
159
|
+
? " (command aborted)"
|
|
160
|
+
: ""
|
|
161
|
+
: "";
|
|
162
|
+
super(
|
|
163
|
+
`${inputText}<output>${result.combined.trim() || `exit code ${result.code}`}${killedText}<output>`,
|
|
164
|
+
);
|
|
135
165
|
this.name = "GhError";
|
|
136
166
|
this.args = args;
|
|
137
167
|
this.code = result.code;
|
|
@@ -196,7 +226,7 @@ export async function pollChecksResult<R extends { code: number; stdout: string
|
|
|
196
226
|
}
|
|
197
227
|
|
|
198
228
|
/** Run `gh` and return stdout. On non-zero exit, throws a `GhError` carrying the toolcall input and raw command. */
|
|
199
|
-
async function ghExec(
|
|
229
|
+
export async function ghExec(
|
|
200
230
|
args: string[],
|
|
201
231
|
ctx: { cwd?: string; signal?: AbortSignal; input?: unknown },
|
|
202
232
|
): Promise<string> {
|