toolcraft 0.0.12 → 0.0.14

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 (27) hide show
  1. package/node_modules/@poe-code/process-runner/README.md +41 -0
  2. package/node_modules/@poe-code/process-runner/dist/docker/args.d.ts +2 -0
  3. package/node_modules/@poe-code/process-runner/dist/docker/args.js +40 -0
  4. package/node_modules/@poe-code/process-runner/dist/docker/context.d.ts +3 -0
  5. package/node_modules/@poe-code/process-runner/dist/docker/context.js +30 -0
  6. package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.d.ts +28 -0
  7. package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.js +428 -0
  8. package/node_modules/@poe-code/process-runner/dist/docker/docker-runner.d.ts +2 -0
  9. package/node_modules/@poe-code/process-runner/dist/docker/docker-runner.js +131 -0
  10. package/node_modules/@poe-code/process-runner/dist/docker/engine.d.ts +3 -0
  11. package/node_modules/@poe-code/process-runner/dist/docker/engine.js +24 -0
  12. package/node_modules/@poe-code/process-runner/dist/host/host-execution-env.d.ts +2 -0
  13. package/node_modules/@poe-code/process-runner/dist/host/host-execution-env.js +48 -0
  14. package/node_modules/@poe-code/process-runner/dist/host/host-runner.d.ts +3 -0
  15. package/node_modules/@poe-code/process-runner/dist/host/host-runner.js +74 -0
  16. package/node_modules/@poe-code/process-runner/dist/index.d.ts +8 -0
  17. package/node_modules/@poe-code/process-runner/dist/index.js +7 -0
  18. package/node_modules/@poe-code/process-runner/dist/testing/index.d.ts +2 -0
  19. package/node_modules/@poe-code/process-runner/dist/testing/index.js +1 -0
  20. package/node_modules/@poe-code/process-runner/dist/testing/mock-runner.d.ts +3 -0
  21. package/node_modules/@poe-code/process-runner/dist/testing/mock-runner.js +115 -0
  22. package/node_modules/@poe-code/process-runner/dist/testing/verify.d.ts +1 -0
  23. package/node_modules/@poe-code/process-runner/dist/testing/verify.js +359 -0
  24. package/node_modules/@poe-code/process-runner/dist/types.d.ts +180 -0
  25. package/node_modules/@poe-code/process-runner/dist/types.js +1 -0
  26. package/node_modules/@poe-code/process-runner/package.json +27 -0
  27. package/package.json +10 -4
@@ -0,0 +1,131 @@
1
+ import * as childProcess from "node:child_process";
2
+ import { randomBytes } from "node:crypto";
3
+ import { buildDockerRunArgs } from "./args.js";
4
+ import { buildContextArgs, detectContext } from "./context.js";
5
+ import { detectEngine } from "./engine.js";
6
+ export function createDockerRunner(options) {
7
+ const engine = options.engine ?? detectEngine();
8
+ const context = options.context ?? detectContext();
9
+ return {
10
+ name: "docker",
11
+ exec(spec) {
12
+ const stdinMode = spec.stdin ?? "ignore";
13
+ const stdoutMode = spec.stdout ?? "pipe";
14
+ const stderrMode = spec.stderr ?? "pipe";
15
+ const interactiveMode = stdinMode === "inherit" &&
16
+ stdoutMode === "inherit" &&
17
+ stderrMode === "inherit" &&
18
+ spec.tty === true;
19
+ const containerName = buildContainerName(options.containerName ?? spec.command);
20
+ const runArgs = buildDockerRunArgs({
21
+ engine,
22
+ context,
23
+ image: options.image,
24
+ command: spec.command,
25
+ args: spec.args ?? [],
26
+ cwd: spec.cwd,
27
+ env: spec.env,
28
+ mounts: options.mounts ?? [],
29
+ ports: options.ports ?? [],
30
+ network: options.network,
31
+ containerName,
32
+ detached: false,
33
+ interactive: stdinMode === "pipe" || stdinMode === "inherit",
34
+ tty: spec.tty ?? false,
35
+ rm: true,
36
+ extraArgs: options.extraArgs ?? []
37
+ });
38
+ const [command, ...args] = runArgs;
39
+ const child = childProcess.spawn(command, args, {
40
+ stdio: interactiveMode ? "inherit" : [stdinMode, stdoutMode, stderrMode]
41
+ });
42
+ let isResultSettled = false;
43
+ let resolveResult = null;
44
+ const result = new Promise((resolve) => {
45
+ resolveResult = resolve;
46
+ });
47
+ const cleanupAbort = bindAbortSignal(spec.signal, () => {
48
+ spawnControlCommand(engine, context, ["stop", containerName]);
49
+ });
50
+ const settleResult = (exitCode) => {
51
+ if (isResultSettled) {
52
+ return;
53
+ }
54
+ isResultSettled = true;
55
+ cleanupAbort();
56
+ resolveResult?.({ exitCode });
57
+ };
58
+ child.once("error", () => {
59
+ settleResult(1);
60
+ });
61
+ child.once("close", (code) => {
62
+ settleResult(code ?? 1);
63
+ });
64
+ return {
65
+ pid: null,
66
+ stdin: interactiveMode ? null : child.stdin,
67
+ stdout: interactiveMode ? null : child.stdout,
68
+ stderr: interactiveMode ? null : child.stderr,
69
+ result,
70
+ kill(signal) {
71
+ if (signal === "SIGKILL") {
72
+ spawnControlCommand(engine, context, ["kill", containerName]);
73
+ return;
74
+ }
75
+ if (signal === undefined || signal === "SIGTERM") {
76
+ spawnControlCommand(engine, context, ["stop", containerName]);
77
+ return;
78
+ }
79
+ spawnControlCommand(engine, context, ["kill", `--signal=${signal}`, containerName]);
80
+ }
81
+ };
82
+ }
83
+ };
84
+ }
85
+ function buildContainerName(name) {
86
+ const suffix = randomBytes(3).toString("hex").slice(0, 6);
87
+ const sanitizedName = sanitizeContainerName(name);
88
+ return `poe-run-${sanitizedName}-${suffix}`;
89
+ }
90
+ function sanitizeContainerName(name) {
91
+ let sanitized = "";
92
+ for (const char of name) {
93
+ if (isContainerNameCharacter(char)) {
94
+ sanitized += char;
95
+ continue;
96
+ }
97
+ sanitized += "-";
98
+ }
99
+ return sanitized.length > 0 ? sanitized : "command";
100
+ }
101
+ function isContainerNameCharacter(char) {
102
+ const code = char.charCodeAt(0);
103
+ if (code >= 48 && code <= 57) {
104
+ return true;
105
+ }
106
+ if (code >= 65 && code <= 90) {
107
+ return true;
108
+ }
109
+ if (code >= 97 && code <= 122) {
110
+ return true;
111
+ }
112
+ return char === "." || char === "_" || char === "-";
113
+ }
114
+ function spawnControlCommand(engine, context, args) {
115
+ childProcess.spawn(engine, [...buildContextArgs(engine, context), ...args], {
116
+ stdio: "ignore"
117
+ });
118
+ }
119
+ function bindAbortSignal(signal, onAbort) {
120
+ if (signal === undefined) {
121
+ return () => { };
122
+ }
123
+ if (signal.aborted) {
124
+ onAbort();
125
+ return () => { };
126
+ }
127
+ signal.addEventListener("abort", onAbort, { once: true });
128
+ return () => {
129
+ signal.removeEventListener("abort", onAbort);
130
+ };
131
+ }
@@ -0,0 +1,3 @@
1
+ import type { Engine } from "../types.js";
2
+ export declare function detectEngine(): Engine;
3
+ export declare function isEngineAvailable(engine: Engine): boolean;
@@ -0,0 +1,24 @@
1
+ import { execSync } from "node:child_process";
2
+ export function detectEngine() {
3
+ if (isEngineAvailable("docker")) {
4
+ return "docker";
5
+ }
6
+ if (isEngineAvailable("podman")) {
7
+ return "podman";
8
+ }
9
+ throw new Error("No container engine found. Please install Docker or Podman:\n" +
10
+ " - Docker Desktop: https://www.docker.com/products/docker-desktop\n" +
11
+ " - Colima (macOS): brew install colima && colima start\n" +
12
+ " - Podman: https://podman.io/docs/installation");
13
+ }
14
+ export function isEngineAvailable(engine) {
15
+ try {
16
+ execSync(`${engine} --version`, {
17
+ stdio: "ignore"
18
+ });
19
+ return true;
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
@@ -0,0 +1,2 @@
1
+ import type { ExecutionEnvFactory } from "../types.js";
2
+ export declare const hostExecutionEnvFactory: ExecutionEnvFactory;
@@ -0,0 +1,48 @@
1
+ import { createHostRunner } from "./host-runner.js";
2
+ export const hostExecutionEnvFactory = {
3
+ type: "host",
4
+ supportsDetach: false,
5
+ async open(openSpec) {
6
+ return {
7
+ id: "host",
8
+ job: null,
9
+ async uploadWorkspace() {
10
+ return {
11
+ files: 0,
12
+ bytes: 0,
13
+ skipped: []
14
+ };
15
+ },
16
+ async downloadWorkspace() {
17
+ return {
18
+ files: 0,
19
+ bytes: 0,
20
+ conflicts: []
21
+ };
22
+ },
23
+ exec(spec) {
24
+ return createHostRunner().exec(spec);
25
+ },
26
+ async detach() {
27
+ throw new Error("host runtime does not support detach because host has no addressable env");
28
+ },
29
+ shell() {
30
+ const shellSpec = openSpec.shellSpec;
31
+ return createHostRunner().exec({
32
+ command: shellSpec?.command ?? openSpec.env.SHELL ?? process.env.SHELL ?? "sh",
33
+ ...(shellSpec?.args ? { args: shellSpec.args } : {}),
34
+ cwd: openSpec.cwd,
35
+ env: shellSpec && "env" in shellSpec ? shellSpec.env : openSpec.env,
36
+ stdin: "inherit",
37
+ stdout: "inherit",
38
+ stderr: "inherit",
39
+ tty: true
40
+ });
41
+ },
42
+ async close() { }
43
+ };
44
+ },
45
+ async attach() {
46
+ throw new Error("host runtime does not support reattach");
47
+ }
48
+ };
@@ -0,0 +1,3 @@
1
+ import type { Runner } from "../types.js";
2
+ import type { HostRunnerOptions } from "../types.js";
3
+ export declare function createHostRunner(options?: HostRunnerOptions): Runner;
@@ -0,0 +1,74 @@
1
+ import { spawn as spawnChildProcess } from "node:child_process";
2
+ export function createHostRunner(options = {}) {
3
+ const detached = options.detached === true;
4
+ return {
5
+ name: "host",
6
+ exec(spec) {
7
+ const stdinMode = spec.stdin ?? "ignore";
8
+ const stdoutMode = spec.stdout ?? "pipe";
9
+ const stderrMode = spec.stderr ?? "pipe";
10
+ const stdio = stdinMode === "inherit" && stdoutMode === "inherit" && stderrMode === "inherit"
11
+ ? "inherit"
12
+ : [stdinMode, stdoutMode, stderrMode];
13
+ const child = spawnChildProcess(spec.command, spec.args ?? [], {
14
+ cwd: spec.cwd,
15
+ env: spec.env,
16
+ stdio,
17
+ ...(detached ? { detached: true } : {})
18
+ });
19
+ if (detached) {
20
+ child.unref();
21
+ }
22
+ const kill = (signal) => {
23
+ if (detached && process.platform !== "win32" && child.pid !== undefined) {
24
+ process.kill(-child.pid, signal);
25
+ return;
26
+ }
27
+ child.kill(signal);
28
+ };
29
+ let settled = false;
30
+ let resolveResult = null;
31
+ const result = new Promise((resolve) => {
32
+ resolveResult = resolve;
33
+ });
34
+ const cleanupAbort = bindAbortSignal(spec.signal, () => {
35
+ kill("SIGTERM");
36
+ });
37
+ child.once("close", (code) => {
38
+ if (settled)
39
+ return;
40
+ settled = true;
41
+ cleanupAbort();
42
+ resolveResult?.({ exitCode: code ?? 1 });
43
+ });
44
+ child.once("error", () => {
45
+ if (settled)
46
+ return;
47
+ settled = true;
48
+ cleanupAbort();
49
+ resolveResult?.({ exitCode: 1 });
50
+ });
51
+ return {
52
+ pid: child.pid ?? null,
53
+ stdin: child.stdin,
54
+ stdout: child.stdout,
55
+ stderr: child.stderr,
56
+ result,
57
+ kill
58
+ };
59
+ }
60
+ };
61
+ }
62
+ function bindAbortSignal(signal, onAbort) {
63
+ if (signal === undefined) {
64
+ return () => { };
65
+ }
66
+ if (signal.aborted) {
67
+ onAbort();
68
+ return () => { };
69
+ }
70
+ signal.addEventListener("abort", onAbort, { once: true });
71
+ return () => {
72
+ signal.removeEventListener("abort", onAbort);
73
+ };
74
+ }
@@ -0,0 +1,8 @@
1
+ export { buildContextArgs, detectContext } from "./docker/context.js";
2
+ export { detectEngine, isEngineAvailable } from "./docker/engine.js";
3
+ export { createDockerRunner } from "./docker/docker-runner.js";
4
+ export { buildDockerRuntimeTemplate, dockerExecutionEnvFactory } from "./docker/docker-execution-env.js";
5
+ export { hostExecutionEnvFactory } from "./host/host-execution-env.js";
6
+ export { createHostRunner } from "./host/host-runner.js";
7
+ export { createMockRunner, createMockRunnerByCommand } from "./testing/index.js";
8
+ export type { DownloadResult, DockerMount, DockerPortMapping, DockerRunArgs, DockerRunnerOptions, Engine, ExecutionState, ExecutionEnvFactory, ExecutionEnvType, HostRunnerOptions, JobHandle, JobStatus, LogChunk, MockRunBehavior, OpenedEnv, OpenSpec, RunHandle, RunResult, Runner, RunSpec, TemplateEntry, UploadResult } from "./types.js";
@@ -0,0 +1,7 @@
1
+ export { buildContextArgs, detectContext } from "./docker/context.js";
2
+ export { detectEngine, isEngineAvailable } from "./docker/engine.js";
3
+ export { createDockerRunner } from "./docker/docker-runner.js";
4
+ export { buildDockerRuntimeTemplate, dockerExecutionEnvFactory } from "./docker/docker-execution-env.js";
5
+ export { hostExecutionEnvFactory } from "./host/host-execution-env.js";
6
+ export { createHostRunner } from "./host/host-runner.js";
7
+ export { createMockRunner, createMockRunnerByCommand } from "./testing/index.js";
@@ -0,0 +1,2 @@
1
+ export { createMockRunner, createMockRunnerByCommand } from "./mock-runner.js";
2
+ export type { MockRunBehavior } from "../types.js";
@@ -0,0 +1 @@
1
+ export { createMockRunner, createMockRunnerByCommand } from "./mock-runner.js";
@@ -0,0 +1,3 @@
1
+ import type { MockRunBehavior, Runner } from "../types.js";
2
+ export declare function createMockRunner(behaviors: MockRunBehavior[]): Runner;
3
+ export declare function createMockRunnerByCommand(behaviorsByCommand: Record<string, MockRunBehavior>): Runner;
@@ -0,0 +1,115 @@
1
+ import { Readable, Writable } from "node:stream";
2
+ export function createMockRunner(behaviors) {
3
+ const remaining = [...behaviors];
4
+ return {
5
+ name: "mock",
6
+ exec(spec) {
7
+ const behavior = remaining.shift();
8
+ if (behavior === undefined) {
9
+ throw new Error("No mock run behaviors left");
10
+ }
11
+ return createRunHandle(spec, behavior);
12
+ }
13
+ };
14
+ }
15
+ export function createMockRunnerByCommand(behaviorsByCommand) {
16
+ return {
17
+ name: "mock",
18
+ exec(spec) {
19
+ const behavior = behaviorsByCommand[spec.command];
20
+ if (behavior === undefined) {
21
+ throw new Error(`No mock run behavior found for command "${spec.command}"`);
22
+ }
23
+ return createRunHandle(spec, behavior);
24
+ }
25
+ };
26
+ }
27
+ function createRunHandle(spec, behavior) {
28
+ const stdoutMode = spec.stdout ?? "pipe";
29
+ const stderrMode = spec.stderr ?? "pipe";
30
+ const stdinMode = spec.stdin ?? "ignore";
31
+ const interval = behavior.stdoutInterval ?? 10;
32
+ const stdoutController = stdoutMode === "pipe" && behavior.stdout !== undefined
33
+ ? createReadableStream(behavior.stdout, interval)
34
+ : null;
35
+ const stderrController = stderrMode === "pipe" && behavior.stderr !== undefined
36
+ ? createReadableStream(behavior.stderr, interval)
37
+ : null;
38
+ let resolveResult = null;
39
+ const result = new Promise((resolve) => {
40
+ resolveResult = resolve;
41
+ });
42
+ let finished = false;
43
+ const complete = () => {
44
+ if (finished || resolveResult === null) {
45
+ return;
46
+ }
47
+ finished = true;
48
+ resolveResult({ exitCode: behavior.exitCode });
49
+ };
50
+ const stopStreams = () => {
51
+ stdoutController?.stop();
52
+ stderrController?.stop();
53
+ };
54
+ const exitAfterMs = behavior.exitAfterMs ?? 0;
55
+ const exitTimer = exitAfterMs > 0
56
+ ? setTimeout(complete, exitAfterMs)
57
+ : queueMicrotask(complete);
58
+ return {
59
+ pid: behavior.pid ?? null,
60
+ stdout: stdoutController?.stream ?? null,
61
+ stderr: stderrController?.stream ?? null,
62
+ stdin: stdinMode === "pipe" ? createWritableStream() : null,
63
+ result,
64
+ kill() {
65
+ if (typeof exitTimer === "object" && exitTimer !== null && "hasRef" in exitTimer) {
66
+ clearTimeout(exitTimer);
67
+ }
68
+ stopStreams();
69
+ complete();
70
+ }
71
+ };
72
+ }
73
+ function createReadableStream(lines, interval) {
74
+ const stream = new Readable({
75
+ read() { }
76
+ });
77
+ const timers = new Set();
78
+ let stopped = false;
79
+ const stop = () => {
80
+ if (stopped) {
81
+ return;
82
+ }
83
+ stopped = true;
84
+ for (const timer of timers) {
85
+ clearTimeout(timer);
86
+ }
87
+ timers.clear();
88
+ stream.push(null);
89
+ };
90
+ if (lines.length === 0) {
91
+ queueMicrotask(stop);
92
+ return { stream, stop };
93
+ }
94
+ for (const [index, line] of lines.entries()) {
95
+ const timer = setTimeout(() => {
96
+ timers.delete(timer);
97
+ if (stopped) {
98
+ return;
99
+ }
100
+ stream.push(line);
101
+ if (index === lines.length - 1) {
102
+ stop();
103
+ }
104
+ }, interval * (index + 1));
105
+ timers.add(timer);
106
+ }
107
+ return { stream, stop };
108
+ }
109
+ function createWritableStream() {
110
+ return new Writable({
111
+ write(_chunk, _encoding, callback) {
112
+ callback();
113
+ }
114
+ });
115
+ }