haiku-atelier-2024/scripts/pull-container-images.ts
2026-04-03 15:21:39 +02:00

63 lines
2.3 KiB
TypeScript

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<string | symbol, any>;
const getObjectKey = (key: string, yaml: ComposeYaml): Effect.Effect<ReadonlyArray<string>, 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 = <A>(filePath: string, schema: Schema.Schema<A>) =>
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<ReadonlyArray<string>, ScriptError> = pipe(
getComposeYaml(COMPOSE_PATH, ComposeSchema),
Effect.flatMap((yaml: ComposeYaml) => getObjectKey("services", yaml)),
Effect.map((keys: ReadonlyArray<string>) => EffectArray.filter(keys, (key) => key !== "wordpress")),
Effect.orElseSucceed(() => [""]),
Effect.tap((services: ReadonlyArray<string>) => {
Bun.spawn({ cmd: ["podman", "compose", "pull", ...services], timeout: 10000 });
return Effect.succeed(services);
}),
Effect.tapCause(Console.error),
);
Effect.runFork(program);