2026-07-08 07:05:48 +08:00
|
|
|
import { initTRPC } from "@trpc/server";
|
|
|
|
|
import EventEmitter, { on } from "node:events";
|
|
|
|
|
import { getConfig } from "./config";
|
2026-07-09 12:33:53 +08:00
|
|
|
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";
|
2026-07-08 07:05:48 +08:00
|
|
|
|
|
|
|
|
// ── tRPC init ──────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
const t = initTRPC.create();
|
|
|
|
|
|
|
|
|
|
// ── Shared EventEmitter for progress ───────────────────
|
|
|
|
|
|
|
|
|
|
const ee = new EventEmitter();
|
|
|
|
|
|
2026-07-09 12:33:53 +08:00
|
|
|
// ── Platform orchestrator ──────────────────────────────
|
2026-07-08 07:05:48 +08:00
|
|
|
|
|
|
|
|
async function runPlatform(
|
|
|
|
|
platform: "modrinth" | "curseforge",
|
|
|
|
|
mcVersions: { mc_version: string }[],
|
|
|
|
|
ctx: PlatformContext,
|
|
|
|
|
): Promise<PlatformResult> {
|
|
|
|
|
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<PublishResult> => {
|
|
|
|
|
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;
|