@zeroclover/pi-rtk 0.0.0 → 0.1.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.
- package/CHANGELOG.md +11 -0
- package/LICENSE +661 -0
- package/README.md +99 -2
- package/package.json +35 -14
- package/src/adapters.ts +67 -0
- package/src/command.ts +58 -0
- package/src/config.ts +107 -0
- package/src/controller.ts +213 -0
- package/src/index.ts +67 -0
- package/src/outcome.ts +63 -0
- package/src/run.ts +63 -0
package/src/outcome.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** How an rtk process ended. */
|
|
2
|
+
export interface RtkRun {
|
|
3
|
+
/** Exit code, or null when the process did not exit normally. */
|
|
4
|
+
code: number | null;
|
|
5
|
+
/** Terminating signal when the process was killed. */
|
|
6
|
+
signal: string | null;
|
|
7
|
+
stdout: string;
|
|
8
|
+
stderr: string;
|
|
9
|
+
timedOut: boolean;
|
|
10
|
+
/** Set when the process could not be started or was cancelled, for example a missing binary. */
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** What `rtk rewrite <command>` decided for one command. */
|
|
15
|
+
export type RewriteOutcome =
|
|
16
|
+
| { kind: "rewrite"; command: string }
|
|
17
|
+
| { kind: "unchanged" }
|
|
18
|
+
| { kind: "block"; reason: string }
|
|
19
|
+
| { kind: "failure"; reason: string };
|
|
20
|
+
|
|
21
|
+
export function firstLine(text: string): string {
|
|
22
|
+
return text.trim().split("\n", 1)[0] ?? "";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Why an rtk process did not complete with an exit code, or undefined when it did. */
|
|
26
|
+
export function runFailure(name: string, run: RtkRun): string | undefined {
|
|
27
|
+
if (run.timedOut) return `${name} timed out`;
|
|
28
|
+
if (run.error !== undefined) return `${name} could not run: ${run.error}`;
|
|
29
|
+
if (run.code === null) return `${name} was killed${run.signal === null ? "" : ` by ${run.signal}`}`;
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Maps the exit-code contract of `rtk rewrite` to an outcome:
|
|
35
|
+
* 0 and 3 print a rewrite (3 only defers permission to the host, which Pi does not have);
|
|
36
|
+
* 1 means no RTK equivalent; 2 is a deny, which blocks only when RTK printed a message,
|
|
37
|
+
* because without one it defers to the host's own deny rules. Anything else is a failure.
|
|
38
|
+
*/
|
|
39
|
+
export function interpretRewrite(original: string, run: RtkRun): RewriteOutcome {
|
|
40
|
+
const failure = runFailure("rtk rewrite", run);
|
|
41
|
+
if (failure !== undefined) return { kind: "failure", reason: failure };
|
|
42
|
+
switch (run.code) {
|
|
43
|
+
case 0:
|
|
44
|
+
case 3: {
|
|
45
|
+
const rewritten = run.stdout.trim();
|
|
46
|
+
if (rewritten === "" || rewritten === original.trim()) return { kind: "unchanged" };
|
|
47
|
+
return { kind: "rewrite", command: rewritten };
|
|
48
|
+
}
|
|
49
|
+
case 1:
|
|
50
|
+
return { kind: "unchanged" };
|
|
51
|
+
case 2: {
|
|
52
|
+
const message = run.stdout.trim();
|
|
53
|
+
return message === "" ? { kind: "unchanged" } : { kind: "block", reason: message };
|
|
54
|
+
}
|
|
55
|
+
default: {
|
|
56
|
+
const detail = firstLine(run.stderr);
|
|
57
|
+
return {
|
|
58
|
+
kind: "failure",
|
|
59
|
+
reason: `rtk rewrite exited with code ${run.code}${detail === "" ? "" : `: ${detail}`}`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/run.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import type { RtkRun } from "./outcome.ts";
|
|
3
|
+
|
|
4
|
+
export interface RunOptions {
|
|
5
|
+
cwd?: string;
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
timeoutMs: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Runs a program without a shell and reports how it ended. */
|
|
11
|
+
export type Run = (file: string, args: string[], options: RunOptions) => Promise<RtkRun>;
|
|
12
|
+
|
|
13
|
+
const MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* `execFile` rather than Pi's `pi.exec`: `pi.exec` reports a missing binary as exit code 1, which is also
|
|
17
|
+
* "no rewrite", and a signal exit as code 0. Never rejects, including for arguments `execFile` refuses.
|
|
18
|
+
*/
|
|
19
|
+
export const runFile: Run = (file, args, options) =>
|
|
20
|
+
new Promise((resolve) => {
|
|
21
|
+
try {
|
|
22
|
+
execFile(
|
|
23
|
+
file,
|
|
24
|
+
args,
|
|
25
|
+
{
|
|
26
|
+
cwd: options.cwd,
|
|
27
|
+
signal: options.signal,
|
|
28
|
+
timeout: options.timeoutMs,
|
|
29
|
+
killSignal: "SIGKILL",
|
|
30
|
+
maxBuffer: MAX_OUTPUT_BYTES,
|
|
31
|
+
windowsHide: true,
|
|
32
|
+
encoding: "utf8",
|
|
33
|
+
},
|
|
34
|
+
(error, stdout, stderr) => {
|
|
35
|
+
const base = { stdout, stderr };
|
|
36
|
+
if (error === null) {
|
|
37
|
+
resolve({ ...base, code: 0, signal: null, timedOut: false });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// A timeout kills the process with killSignal and sets `killed`.
|
|
41
|
+
if (error.killed && options.signal?.aborted !== true) {
|
|
42
|
+
resolve({ ...base, code: null, signal: error.signal ?? null, timedOut: true });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (typeof error.code === "number") {
|
|
46
|
+
resolve({ ...base, code: error.code, signal: null, timedOut: false });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (error.signal) {
|
|
50
|
+
resolve({ ...base, code: null, signal: error.signal, timedOut: false });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Spawn failures (ENOENT, EACCES, missing cwd), cancellation, and output over maxBuffer.
|
|
54
|
+
const code = typeof error.code === "string" ? `${error.code}: ` : "";
|
|
55
|
+
resolve({ ...base, code: null, signal: null, timedOut: false, error: `${code}${error.message}` });
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
// Invalid arguments, such as a NUL byte in the command, throw before the process starts.
|
|
60
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
61
|
+
resolve({ stdout: "", stderr: "", code: null, signal: null, timedOut: false, error: message });
|
|
62
|
+
}
|
|
63
|
+
});
|