Skip to content

Commit aed8595

Browse files
committed
Fix subprocess tests: export format + base64 encoding bug
- Changed all test fixtures from 'export default async function main()' to 'export default async () =>' to match funee's expected format - Fixed base64Encode padding logic bug that added extra null bytes when input length wasn't divisible by 3
1 parent c94b24a commit aed8595

17 files changed

Lines changed: 782 additions & 10 deletions

Cargo.lock

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

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,7 @@ hyper = { version = "1.0", features = ["server", "http1"] }
4444
hyper-util = { version = "0.1", features = ["tokio", "server", "server-auto"] }
4545
http-body-util = "0.1"
4646
bytes = "1.0"
47+
48+
# Unix process signals
49+
[target.'cfg(unix)'.dependencies]
50+
nix = { version = "0.29", features = ["signal"] }

funee-lib/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,3 +666,17 @@ export type {
666666
} from "./server/index.ts";
667667

668668
export { serve } from "./server/index.ts";
669+
670+
// ============================================================================
671+
// Subprocess - Child Process Management
672+
// ============================================================================
673+
674+
export type {
675+
SpawnOptions,
676+
ProcessStatus,
677+
CommandOutput,
678+
Process,
679+
Signal,
680+
} from "./process/index.ts";
681+
682+
export { spawn } from "./process/index.ts";

funee-lib/process/index.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Subprocess API for funee
3+
*
4+
* Provides spawn() for running and managing child processes.
5+
* The actual implementation is provided by the runtime bootstrap.
6+
*
7+
* @example
8+
* ```typescript
9+
* import { spawn } from "funee";
10+
*
11+
* // Simple usage - returns Promise<CommandOutput>
12+
* const result = await spawn("echo", ["hello world"]);
13+
* console.log(result.stdoutText()); // "hello world\n"
14+
* console.log(result.status.code); // 0
15+
*
16+
* // With options - returns Process handle
17+
* const proc = spawn({
18+
* cmd: ["cat"],
19+
* stdin: "piped",
20+
* stdout: "piped",
21+
* });
22+
* await proc.writeInput("hello");
23+
* const output = await proc.output();
24+
* console.log(output.stdoutText()); // "hello"
25+
* ```
26+
*/
27+
28+
import type { SpawnOptions, ProcessStatus, CommandOutput, Process, Signal } from "./types.ts";
29+
30+
// Re-export types
31+
export type { SpawnOptions, ProcessStatus, CommandOutput, Process, Signal } from "./types.ts";
32+
33+
// Declare the global spawn function (provided by runtime bootstrap)
34+
declare global {
35+
function spawn(command: string, args?: string[]): Promise<CommandOutput>;
36+
function spawn(options: SpawnOptions): Process;
37+
}
38+
39+
/**
40+
* Spawn a subprocess.
41+
*
42+
* Two calling styles supported:
43+
*
44+
* 1. Simple form - runs command and waits for output:
45+
* `const result = await spawn("echo", ["hello"]);`
46+
* Returns: Promise<CommandOutput>
47+
*
48+
* 2. Options form - returns Process handle for streaming:
49+
* `const proc = spawn({ cmd: ["cat"], stdin: "piped" });`
50+
* Returns: Process
51+
*
52+
* @example
53+
* ```typescript
54+
* // Simple: capture output
55+
* const result = await spawn("ls", ["-la"]);
56+
* console.log(result.stdoutText());
57+
* console.log("Exit code:", result.status.code);
58+
*
59+
* // Advanced: streaming with stdin
60+
* const proc = spawn({
61+
* cmd: ["grep", "ERROR"],
62+
* stdin: "piped",
63+
* stdout: "piped",
64+
* });
65+
* await proc.writeInput("ERROR: something bad\nINFO: ok\n");
66+
* const filtered = await proc.output();
67+
* console.log(filtered.stdoutText()); // "ERROR: something bad\n"
68+
* ```
69+
*/
70+
export const spawn = globalThis.spawn;

funee-lib/process/types.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* Subprocess Types
3+
*
4+
* TypeScript interfaces for funee's subprocess API.
5+
*/
6+
7+
/**
8+
* Signal types supported for process killing
9+
*/
10+
export type Signal =
11+
| "SIGTERM"
12+
| "SIGKILL"
13+
| "SIGINT"
14+
| "SIGHUP"
15+
| "SIGQUIT";
16+
17+
/**
18+
* Options for spawning a subprocess
19+
*/
20+
export interface SpawnOptions {
21+
/** Command and arguments as array */
22+
cmd: string[];
23+
24+
/** Working directory for the process */
25+
cwd?: string;
26+
27+
/** Environment variables (replaces or merges with process env) */
28+
env?: Record<string, string>;
29+
30+
/** Inherit environment and merge with env option (default: true) */
31+
inheritEnv?: boolean;
32+
33+
/** How to handle stdin: "piped" | "inherit" | "null" (default: "null") */
34+
stdin?: "piped" | "inherit" | "null";
35+
36+
/** How to handle stdout: "piped" | "inherit" | "null" (default: "piped") */
37+
stdout?: "piped" | "inherit" | "null";
38+
39+
/** How to handle stderr: "piped" | "inherit" | "null" (default: "piped") */
40+
stderr?: "piped" | "inherit" | "null";
41+
}
42+
43+
/**
44+
* Process exit status
45+
*/
46+
export interface ProcessStatus {
47+
/** True if process exited with code 0 */
48+
success: boolean;
49+
50+
/** Exit code (null if terminated by signal) */
51+
code: number | null;
52+
53+
/** Signal that terminated the process (null if normal exit) */
54+
signal: Signal | null;
55+
}
56+
57+
/**
58+
* Output from a completed process
59+
*/
60+
export interface CommandOutput {
61+
/** Process exit status */
62+
status: ProcessStatus;
63+
64+
/** Stdout as Uint8Array (empty if stdout not piped) */
65+
stdout: Uint8Array;
66+
67+
/** Stderr as Uint8Array (empty if stderr not piped) */
68+
stderr: Uint8Array;
69+
70+
/** Convenience: stdout decoded as UTF-8 string */
71+
stdoutText(): string;
72+
73+
/** Convenience: stderr decoded as UTF-8 string */
74+
stderrText(): string;
75+
}
76+
77+
/**
78+
* Handle to a running subprocess
79+
*/
80+
export interface Process {
81+
/** Process ID (OS-level PID) */
82+
readonly pid: number;
83+
84+
/** Promise that resolves with ProcessStatus when process exits */
85+
readonly status: Promise<ProcessStatus>;
86+
87+
/** Send a signal to the process */
88+
kill(signal?: Signal): void;
89+
90+
/** Wait for process and collect all output */
91+
output(): Promise<CommandOutput>;
92+
93+
/** Write data to stdin and close it */
94+
writeInput(data: string | Uint8Array): Promise<void>;
95+
}

0 commit comments

Comments
 (0)