import { initTRPC } from "@trpc/server"; import EventEmitter, { on } from "node:events"; import { getConfig } from "./config"; import { errorMessage } from "@/lib/utils/error"; import { PublishInputSchema, type ProgressEvent, type PlatformResult, type PublishResult, } from "./publish.schemas"; import { publishModrinthVersion } from "./platforms/modrinth"; import { publishCurseForgeVersion } from "./platforms/curseforge"; import type { PlatformContext } from "./platforms/types"; // ── tRPC init ────────────────────────────────────────── const t = initTRPC.create(); // ── Shared EventEmitter for progress ─────────────────── const ee = new EventEmitter(); // ── Platform orchestrator ────────────────────────────── async function runPlatform( platform: "modrinth" | "curseforge", mcVersions: { mc_version: string }[], ctx: PlatformContext, ): Promise { const result: PlatformResult = { success: 0, fail: 0, errors: [] }; const total = mcVersions.length; if (total === 0) { ee.emit("progress", { platform, current: 0, total: 0, status: "completed", errors: [], } satisfies ProgressEvent); return result; } // Emit initial running state ee.emit("progress", { platform, current: 0, total, status: "running", errors: [], } satisfies ProgressEvent); const publishFn = platform === "modrinth" ? publishModrinthVersion : publishCurseForgeVersion; for (let i = 0; i < mcVersions.length; i++) { const { mc_version } = mcVersions[i]; try { await publishFn(ctx, mc_version); result.success++; const isLast = i + 1 === total; ee.emit("progress", { platform, current: i + 1, total, status: isLast ? "completed" : "running", errors: result.errors, } satisfies ProgressEvent); } catch (err) { result.fail++; const msg = `${mc_version}: ${errorMessage(err)}`; result.errors.push(msg); ee.emit("progress", { platform, current: i, total, status: "failed", errors: result.errors, } satisfies ProgressEvent); // Cancel remaining for this platform only break; } } return result; } // ── Router ───────────────────────────────────────────── export const appRouter = t.router({ publish: t.procedure .input(PublishInputSchema) .mutation(async ({ input }): Promise => { const config = await getConfig(input.configName); const ctx: PlatformContext = { input, config }; const [modrinthSettled, curseforgeSettled] = await Promise.allSettled([ runPlatform("modrinth", input.mcVersions.modrinth, ctx), runPlatform("curseforge", input.mcVersions.curseforge, ctx), ]); const modrinth: PlatformResult = modrinthSettled.status === "fulfilled" ? modrinthSettled.value : { success: 0, fail: 0, errors: [errorMessage(modrinthSettled.reason)] }; const curseforge: PlatformResult = curseforgeSettled.status === "fulfilled" ? curseforgeSettled.value : { success: 0, fail: 0, errors: [errorMessage(curseforgeSettled.reason)] }; return { modrinth, curseforge }; }), progress: t.procedure.subscription(async function* (opts) { for await (const [data] of on(ee, "progress", { signal: opts.signal, })) { yield data as ProgressEvent; } }), }); export type AppRouter = typeof appRouter;