1import * as Effect from "effect/Effect";
2import * as FileSystem from "effect/FileSystem";
3import * as Stream from "effect/Stream";
4import * as ChildProcess from "effect/process/ChildProcess";
5import { ChildProcessSpawner } from "effect/process/ChildProcessSpawner";
6
7import * as Cloudflare from "alchemy/Cloudflare";
8
9export class DevBox extends Cloudflare.Container<
10 DevBox,
11 {
12 readFile: (path: string) => Effect.Effect<string>;
13 writeFile: (path: string, contents: string) => Effect.Effect<void>;
14 exec: (command: string) => Effect.Effect<{
15 exitCode: number;
16 stdout: string;
17 stderr: string;
18 }>;
19 }
20>()("DevBox") {}
21
22export default DevBox.make(
23 {
24 main: import.meta.url,
25 image: "oven/bun:1.3",
26 },
27 Effect.gen(function* () {
28 const fs = yield* FileSystem.FileSystem;
29 const cp = yield* ChildProcessSpawner;
30
31 return {
32 readFile: (path: string) => fs.readFileString(path).pipe(Effect.orDie),
33 writeFile: (path: string, contents: string) =>
34 fs.writeFileString(path, contents).pipe(Effect.orDie),
35 exec: (command: string) =>
36 cp.spawn(ChildProcess.make(command, { shell: true })).pipe(
37 Effect.flatMap(({ exitCode, stdout, stderr }) =>
38 Effect.all({
39 exitCode,
40 stdout: stdout.pipe(Stream.decodeText, Stream.mkString),
41 stderr: stderr.pipe(Stream.decodeText, Stream.mkString),
42 }),
43 ),
44 Effect.scoped,
45 Effect.orDie,
46 ),
47 };
48 }),
49);