2026-04-05

This commit is contained in:
gcch 2026-04-05 13:11:21 +02:00
commit 949195caf8
71 changed files with 535 additions and 458 deletions

View file

@ -1,45 +1,69 @@
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 { Console, Data, Effect, Array as EffectArray, 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>("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.Any);
// Const composeSchema = Schema.Record(Schema.Union([Schema.String, Schema.Symbol]), Schema.Unknown);
// type YamlRecord = ReadonlyRecord<string | symbol, unknown>;
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 })),
);
/**
* 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<string> = (compose) =>
Record.keys(compose.services);
const getFileContent = Effect.fn("getFileContent")(function* (filePath: string) {
const fileRef = Bun.file(filePath);
/**
* 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<string, ScriptError> = Effect.fn("getFileContent")(
function* (filePath) {
const fileRef: Bun.BunFile = Bun.file(filePath);
yield* Effect.tryPromise({
try: () => fileRef.exists(),
catch: (_) => new ScriptError({ cause: "The wanted file does not exist." }),
});
yield* Effect.tryPromise({
catch: (_): ScriptError => new ScriptError({ cause: "The wanted file does not exist." }),
try: async (): Promise<boolean> => fileRef.exists(),
});
return yield* Effect.tryPromise({
try: () => fileRef.text(),
catch: (_) => new ScriptError({ cause: "Can't retrieve the file's text content." }),
});
});
return yield* Effect.tryPromise({
catch: (_): ScriptError => new ScriptError({ cause: "Can't retrieve the file's text content." }),
try: async (): Promise<string> => fileRef.text(),
});
},
);
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) => {
/**
* 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: <ComposeSchema>(
path: string,
schema: Schema.Schema<ComposeSchema>,
) => Effect.Effect<ComposeSchema, ScriptError, unknown> = 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 {
@ -47,14 +71,15 @@ const getComposeYaml = <A>(filePath: string, schema: Schema.Schema<A>) =>
}
}),
);
});
const program: Effect.Effect<ReadonlyArray<string>, ScriptError> = pipe(
getComposeYaml(COMPOSE_PATH, ComposeSchema),
Effect.flatMap((yaml: ComposeYaml) => getObjectKey("services", yaml)),
getComposeYaml(COMPOSE_PATH, Compose),
Effect.map((compose: Compose) => getServicesFromComposeYaml(compose)),
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 });
Bun.spawn({ cmd: ["podman", "compose", "pull", ...services], timeout: DEFAULT_CMD_TIMEOUT });
return Effect.succeed(services);
}),
Effect.tapCause(Console.error),