import { YAML } from "bun"; import { Array as EffectArray, Console, Data, Effect, pipe, Record, Schema, SchemaIssue } from "effect"; import { NoSuchElementError } from "effect/Cause"; import { type ReadonlyRecord } from "effect/Record"; import { SchemaError } from "effect/Schema"; const COMPOSE_PATH = "compose.yaml"; class ScriptError extends Data.TaggedError("ScriptError")<{ cause: unknown }> {} const ComposeSchema = Schema.Record(Schema.Union([Schema.String, Schema.Symbol]), Schema.Any); type ComposeYaml = ReadonlyRecord; const getObjectKey = (key: string, yaml: ComposeYaml): Effect.Effect, ScriptError> => pipe( Record.get(key)(yaml), Effect.fromOption, Effect.map((yaml: any) => Record.keys(yaml)), Effect.mapError((e: NoSuchElementError) => new ScriptError({ cause: e })), ); const getFileContent = Effect.fn("getFileContent")(function* (filePath: string) { const fileRef = Bun.file(filePath); yield* Effect.tryPromise({ try: () => fileRef.exists(), catch: (_) => new ScriptError({ cause: "The wanted file does not exist." }), }); return yield* Effect.tryPromise({ try: () => fileRef.text(), catch: (_) => new ScriptError({ cause: "Can't retrieve the file's text content." }), }); }); const getComposeYaml = (filePath: string, schema: Schema.Schema) => pipe( getFileContent(filePath), Effect.map((text: string) => YAML.parse(text)), Effect.flatMap((yaml: unknown) => Schema.decodeUnknownEffect(schema)(yaml, { errors: "all" })), Effect.mapError((error) => { if (error instanceof SchemaError) { return new ScriptError({ cause: SchemaIssue.makeFormatterStandardSchemaV1()(error.issue) }); } else { return error; } }), ); const program: Effect.Effect, ScriptError> = pipe( getComposeYaml(COMPOSE_PATH, ComposeSchema), Effect.flatMap((yaml: ComposeYaml) => getObjectKey("services", yaml)), Effect.map((keys: ReadonlyArray) => EffectArray.filter(keys, (key) => key !== "wordpress")), Effect.orElseSucceed(() => [""]), Effect.tap((services: ReadonlyArray) => { Bun.spawn({ cmd: ["podman", "compose", "pull", ...services], timeout: 10000 }); return Effect.succeed(services); }), Effect.tapCause(Console.error), ); Effect.runFork(program);