import { YAML } from "bun"; import { Array as EffectArray, Console, Data, Effect, pipe, Record, Schema, SchemaIssue } from "effect"; import { SchemaError } from "effect/Schema"; const COMPOSE_PATH = "compose.yaml"; const DEFAULT_CMD_TIMEOUT = 10_000; class Compose extends Schema.Class("Compose")({ name: Schema.String, services: Schema.Record(Schema.String, Schema.Unknown), }) {} class ScriptError extends Data.TaggedError("ScriptError")<{ cause: unknown }> {} // Const composeSchema = Schema.Record(Schema.Union([Schema.String, Schema.Symbol]), Schema.Unknown); // type YamlRecord = ReadonlyRecord; /* */ /** * Retourne les noms des services déclarés dans un ficher _Compose_. * @param compose Le fichier _Compose_ sous forme d'objet. * @returns Les noms des Services sous forme de tableau. */ const getServicesFromComposeYaml: (compose: Compose) => ReadonlyArray = compose => Record.keys(compose.services); /** * Récupère le contenu texte d'un fichier pour un chemin donné. * * @param filePath Le chemin du fichier dont on souhaite le contenu. * @returns Le contenu textuel du fichier sous forme de chaîne de caractères. */ const getFileContent: (filePath: string) => Effect.Effect = Effect.fn("getFileContent")( function*(filePath) { const fileRef: Bun.BunFile = Bun.file(filePath); yield* Effect.tryPromise({ catch: (_): ScriptError => new ScriptError({ cause: "The wanted file does not exist." }), try: async (): Promise => fileRef.exists(), }); return yield* Effect.tryPromise({ catch: (_): ScriptError => new ScriptError({ cause: "Can't retrieve the file's text content." }), try: async (): Promise => fileRef.text(), }); }, ); /** * Récupère le contenu _YAML_ d'un fichier _Compose_ sous forme de `Record`. * * @param path Le chemin du fichier _Compose_. * @param schema Le `Schema` utilisée pour le parsage des données du fichier. * @returns Un `Record` des données du fichier. */ const getComposeYaml: ( path: string, schema: Schema.Schema, ) => Effect.Effect = Effect.fn("getComposeYaml")(function*(path, schema) { return yield* pipe( getFileContent(path), Effect.map((text: string): unknown => YAML.parse(text)), Effect.flatMap((yaml: unknown) => Schema.decodeUnknownEffect(schema)(yaml, { errors: "all", onExcessProperty: "ignore" }) ), Effect.mapError((error): ScriptError => { if (error instanceof SchemaError) { return new ScriptError({ cause: SchemaIssue.makeFormatterStandardSchemaV1()(error.issue) }); } else { return error; } }), ); }); const program: Effect.Effect, ScriptError> = pipe( getComposeYaml(COMPOSE_PATH, Compose), Effect.map((compose: Compose) => getServicesFromComposeYaml(compose)), Effect.map((keys: ReadonlyArray) => EffectArray.filter(keys, key => key !== "wordpress")), Effect.orElseSucceed(() => [""]), Effect.tap((services: ReadonlyArray) => { Bun.spawn({ cmd: ["podman", "compose", "pull", ...services], timeout: DEFAULT_CMD_TIMEOUT }); return Effect.succeed(services); }), Effect.tapCause(Console.error), ); Effect.runFork(program);