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,
|
2026-07-11 09:23:12 +08:00
|
|
|
platforms,
|
|
|
|
|
type PlatformContext,
|
|
|
|
|
type PlatformAdaptor,
|
2026-07-13 09:29:00 +08:00
|
|
|
type PublishInput,
|
|
|
|
|
PublishResultSchema,
|
|
|
|
|
ProgressEventSchema,
|
2026-07-09 12:33:53 +08:00
|
|
|
} from "./publish.schemas";
|
|
|
|
|
import { publishModrinthVersion } from "./platforms/modrinth";
|
|
|
|
|
import { publishCurseForgeVersion } from "./platforms/curseforge";
|
2026-07-11 09:23:12 +08:00
|
|
|
import { computeVersionRanges } from "@/lib/utils/mcVersion";
|
|
|
|
|
import { getCurseForgeMcVersions, getModrinthMcVersions } from "./meta";
|
|
|
|
|
import { readAsFile } from "@/lib/utils/file";
|
|
|
|
|
import { Template } from "@/lib/utils/template";
|
|
|
|
|
import { buildJarPath } from "@/lib/utils/format";
|
|
|
|
|
|
|
|
|
|
export const platformAdaptors: {
|
|
|
|
|
[key in typeof platforms[number]]: PlatformAdaptor
|
|
|
|
|
} = {
|
|
|
|
|
modrinth: {
|
|
|
|
|
publish: publishModrinthVersion,
|
|
|
|
|
getGameVersions: getModrinthMcVersions,
|
|
|
|
|
},
|
|
|
|
|
curseforge: {
|
|
|
|
|
publish: publishCurseForgeVersion,
|
|
|
|
|
getGameVersions: getCurseForgeMcVersions,
|
|
|
|
|
},
|
|
|
|
|
};
|
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(
|
2026-07-11 09:23:12 +08:00
|
|
|
platform: typeof platforms[number],
|
2026-07-13 09:29:00 +08:00
|
|
|
mcVersions: PublishInput["mcVersions"][keyof PublishInput["mcVersions"]],
|
2026-07-08 07:05:48 +08:00
|
|
|
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);
|
|
|
|
|
|
2026-07-11 09:23:12 +08:00
|
|
|
const publishFn = platformAdaptors[platform].publish;
|
|
|
|
|
const getGameVersionsFn = platformAdaptors[platform].getGameVersions;
|
2026-07-08 07:05:48 +08:00
|
|
|
|
2026-07-11 09:23:12 +08:00
|
|
|
const mcVersionEntries = computeVersionRanges(
|
|
|
|
|
mcVersions.map(v => v.mc_version),
|
|
|
|
|
await getGameVersionsFn(),
|
|
|
|
|
ctx.input.cutoffMcVersion
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const primaryFileTemplate = new Template(ctx.config.filename_format);
|
|
|
|
|
const sourceFileTemplate = new Template(ctx.config.source_filename_format);
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < mcVersionEntries.length; i++) {
|
|
|
|
|
const mcVersionEntry = mcVersionEntries[i];
|
2026-07-08 07:05:48 +08:00
|
|
|
|
|
|
|
|
try {
|
2026-07-11 09:23:12 +08:00
|
|
|
const primaryFile = await readAsFile(buildJarPath(ctx.config.project_dir, primaryFileTemplate, ctx.input.version, mcVersionEntry.version));
|
|
|
|
|
const sourceFile = await readAsFile(buildJarPath(ctx.config.project_dir, sourceFileTemplate, ctx.input.version, mcVersionEntry.version));
|
|
|
|
|
|
|
|
|
|
await publishFn(ctx, mcVersionEntry, {
|
|
|
|
|
primary: primaryFile,
|
|
|
|
|
source: sourceFile,
|
|
|
|
|
});
|
2026-07-08 07:05:48 +08:00
|
|
|
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++;
|
2026-07-11 09:23:12 +08:00
|
|
|
const msg = `${mcVersionEntry.version}: ${errorMessage(err)}`;
|
2026-07-08 07:05:48 +08:00
|
|
|
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 };
|
|
|
|
|
|
2026-07-11 09:43:15 +08:00
|
|
|
const results = await Promise.all(platforms.map(async (platform) => {
|
|
|
|
|
try {
|
|
|
|
|
const result = await runPlatform(platform, input.mcVersions[platform], ctx);
|
|
|
|
|
return {platform, result};
|
|
|
|
|
} catch (err) {
|
|
|
|
|
return {
|
|
|
|
|
platform,
|
|
|
|
|
result: { success: 0, fail: 0, errors: [errorMessage(err)] }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
|
2026-07-13 09:29:00 +08:00
|
|
|
return PublishResultSchema.parse(Object.fromEntries(results.map(result => [result.platform, result.result])));
|
2026-07-08 07:05:48 +08:00
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
progress: t.procedure.subscription(async function* (opts) {
|
|
|
|
|
for await (const [data] of on(ee, "progress", {
|
|
|
|
|
signal: opts.signal,
|
|
|
|
|
})) {
|
2026-07-13 09:29:00 +08:00
|
|
|
yield ProgressEventSchema.parse(data);
|
2026-07-08 07:05:48 +08:00
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type AppRouter = typeof appRouter;
|