Extract platform publishing logic into dedicated modules
This commit is contained in:
parent
db1cb28296
commit
e57a1ac993
3
src/lib/utils/error.ts
Normal file
3
src/lib/utils/error.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export function errorMessage(err: unknown): string {
|
||||||
|
return err instanceof Error ? err.message : String(err);
|
||||||
|
}
|
||||||
11
src/lib/utils/file.ts
Normal file
11
src/lib/utils/file.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
|
||||||
|
export function readJarBlob(filePath: string): Blob {
|
||||||
|
try {
|
||||||
|
const buffer = readFileSync(filePath);
|
||||||
|
return new Blob([buffer]);
|
||||||
|
} catch (err) {
|
||||||
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
|
throw new Error(`Failed to read file "${filePath}": ${msg}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/lib/utils/format.ts
Normal file
22
src/lib/utils/format.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
import { Template } from "@/lib/utils/template";
|
||||||
|
|
||||||
|
export function resolveVersionName(
|
||||||
|
template: string,
|
||||||
|
templates: Record<string, string> | undefined,
|
||||||
|
values: Record<string, string>,
|
||||||
|
): string {
|
||||||
|
const tmpl = new Template(template, templates);
|
||||||
|
return tmpl.format(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildJarPath(
|
||||||
|
projectDir: string,
|
||||||
|
format: string,
|
||||||
|
version: string,
|
||||||
|
mcVersion: string,
|
||||||
|
): string {
|
||||||
|
const tmpl = new Template(format);
|
||||||
|
const filename = tmpl.format({ version, mc_version: mcVersion });
|
||||||
|
return path.join(projectDir, "build", "libs", filename);
|
||||||
|
}
|
||||||
43
src/services/platforms/curseforge.ts
Normal file
43
src/services/platforms/curseforge.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { curseforgeClient } from "@/services/clients";
|
||||||
|
import { uploadFile } from "@/lib/curseforge";
|
||||||
|
import { resolveVersionName, buildJarPath } from "@/lib/utils/format";
|
||||||
|
import { readJarBlob } from "@/lib/utils/file";
|
||||||
|
import type { UploadMetadata } from "@/types";
|
||||||
|
import type { PlatformContext } from "./types";
|
||||||
|
|
||||||
|
export async function publishCurseForgeVersion(
|
||||||
|
ctx: PlatformContext,
|
||||||
|
mcVersion: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const { input, config } = ctx;
|
||||||
|
|
||||||
|
const displayName = resolveVersionName(
|
||||||
|
config.curseforge.version_name,
|
||||||
|
{ filename_format: config.filename_format },
|
||||||
|
{ version: input.version, mc_version: mcVersion },
|
||||||
|
);
|
||||||
|
|
||||||
|
const primaryPath = buildJarPath(
|
||||||
|
config.project_dir,
|
||||||
|
config.filename_format,
|
||||||
|
input.version,
|
||||||
|
mcVersion,
|
||||||
|
);
|
||||||
|
|
||||||
|
const primaryBlob = readJarBlob(primaryPath);
|
||||||
|
|
||||||
|
const metadata: UploadMetadata = {
|
||||||
|
changelog: input.changelog,
|
||||||
|
changelogType: "markdown",
|
||||||
|
displayName,
|
||||||
|
releaseType: input.versionType,
|
||||||
|
relations: config.curseforge.relations,
|
||||||
|
};
|
||||||
|
|
||||||
|
await uploadFile(
|
||||||
|
curseforgeClient,
|
||||||
|
config.curseforge.project_id,
|
||||||
|
metadata,
|
||||||
|
primaryBlob,
|
||||||
|
);
|
||||||
|
}
|
||||||
70
src/services/platforms/modrinth.ts
Normal file
70
src/services/platforms/modrinth.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { modrinthClient } from "@/services/clients";
|
||||||
|
import { createVersion } from "@/lib/modrinth";
|
||||||
|
import { resolveVersionName, buildJarPath } from "@/lib/utils/format";
|
||||||
|
import { readJarBlob } from "@/lib/utils/file";
|
||||||
|
import type { CreatableVersion } from "@/types";
|
||||||
|
import type { PlatformContext } from "./types";
|
||||||
|
|
||||||
|
export async function publishModrinthVersion(
|
||||||
|
ctx: PlatformContext,
|
||||||
|
mcVersion: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const { input, config } = ctx;
|
||||||
|
|
||||||
|
const versionNumber = resolveVersionName(
|
||||||
|
config.modrinth.version,
|
||||||
|
undefined,
|
||||||
|
{ version: input.version, mc_version: mcVersion },
|
||||||
|
);
|
||||||
|
|
||||||
|
const versionName = resolveVersionName(
|
||||||
|
config.modrinth.version_name,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
version: input.version,
|
||||||
|
mc_version: mcVersion,
|
||||||
|
mc_version_range: mcVersion,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const primaryPath = buildJarPath(
|
||||||
|
config.project_dir,
|
||||||
|
config.filename_format,
|
||||||
|
input.version,
|
||||||
|
mcVersion,
|
||||||
|
);
|
||||||
|
const sourcePath = buildJarPath(
|
||||||
|
config.project_dir,
|
||||||
|
config.source_filename_format,
|
||||||
|
input.version,
|
||||||
|
mcVersion,
|
||||||
|
);
|
||||||
|
|
||||||
|
const primaryBlob = readJarBlob(primaryPath);
|
||||||
|
const sourceBlob = readJarBlob(sourcePath);
|
||||||
|
|
||||||
|
const data: CreatableVersion = {
|
||||||
|
project_id: config.modrinth.project_id,
|
||||||
|
name: versionName,
|
||||||
|
version_number: versionNumber,
|
||||||
|
game_versions: [mcVersion],
|
||||||
|
version_type: input.versionType,
|
||||||
|
loaders: config.modrinth.loaders,
|
||||||
|
featured: false,
|
||||||
|
dependencies: config.modrinth.dependencies.map((d) => ({
|
||||||
|
version_id: null,
|
||||||
|
project_id: d.project_id,
|
||||||
|
file_name: null,
|
||||||
|
dependency_type: d.dependency_type,
|
||||||
|
})),
|
||||||
|
file_parts: ["primary", "sources"],
|
||||||
|
primary_file: "primary",
|
||||||
|
file_types: { sources: "sources-jar" },
|
||||||
|
environment: config.modrinth.environment,
|
||||||
|
};
|
||||||
|
|
||||||
|
await createVersion(modrinthClient, data, {
|
||||||
|
primary: primaryBlob,
|
||||||
|
sources: sourceBlob,
|
||||||
|
});
|
||||||
|
}
|
||||||
7
src/services/platforms/types.ts
Normal file
7
src/services/platforms/types.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import type { PublishInput } from "../publish.schemas";
|
||||||
|
import type { getConfig } from "@/services/config";
|
||||||
|
|
||||||
|
export interface PlatformContext {
|
||||||
|
input: PublishInput;
|
||||||
|
config: Awaited<ReturnType<typeof getConfig>>;
|
||||||
|
}
|
||||||
40
src/services/publish.schemas.ts
Normal file
40
src/services/publish.schemas.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
// ── Schemas ────────────────────────────────────────────
|
||||||
|
|
||||||
|
export const PublishInputSchema = z.object({
|
||||||
|
configName: z.string(),
|
||||||
|
version: z.string(),
|
||||||
|
mcVersions: z.object({
|
||||||
|
modrinth: z.array(z.object({ mc_version: z.string() })),
|
||||||
|
curseforge: z.array(z.object({ mc_version: z.string() })),
|
||||||
|
}),
|
||||||
|
changelog: z.string(),
|
||||||
|
versionType: z.enum(["release", "beta", "alpha"]),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ProgressEventSchema = z.object({
|
||||||
|
platform: z.enum(["modrinth", "curseforge"]),
|
||||||
|
current: z.number().int().min(0),
|
||||||
|
total: z.number().int().min(0),
|
||||||
|
status: z.enum(["running", "completed", "failed"]),
|
||||||
|
errors: z.array(z.string()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const PlatformResultSchema = z.object({
|
||||||
|
success: z.number().int().min(0),
|
||||||
|
fail: z.number().int().min(0),
|
||||||
|
errors: z.array(z.string()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const PublishResultSchema = z.object({
|
||||||
|
modrinth: PlatformResultSchema,
|
||||||
|
curseforge: PlatformResultSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Inferred types ─────────────────────────────────────
|
||||||
|
|
||||||
|
export type PublishInput = z.infer<typeof PublishInputSchema>;
|
||||||
|
export type ProgressEvent = z.infer<typeof ProgressEventSchema>;
|
||||||
|
export type PlatformResult = z.infer<typeof PlatformResultSchema>;
|
||||||
|
export type PublishResult = z.infer<typeof PublishResultSchema>;
|
||||||
@ -1,53 +1,16 @@
|
|||||||
import { initTRPC } from "@trpc/server";
|
import { initTRPC } from "@trpc/server";
|
||||||
import { z } from "zod";
|
|
||||||
import EventEmitter, { on } from "node:events";
|
import EventEmitter, { on } from "node:events";
|
||||||
import { readFileSync } from "node:fs";
|
|
||||||
import path from "node:path";
|
|
||||||
import { getConfig } from "./config";
|
import { getConfig } from "./config";
|
||||||
import { modrinthClient, curseforgeClient } from "./clients";
|
import { errorMessage } from "@/lib/utils/error";
|
||||||
import { createVersion } from "@/lib/modrinth";
|
import {
|
||||||
import { uploadFile } from "@/lib/curseforge";
|
PublishInputSchema,
|
||||||
import { Template } from "@/lib/utils/template";
|
type ProgressEvent,
|
||||||
import type { CreatableVersion, UploadMetadata } from "@/types";
|
type PlatformResult,
|
||||||
|
type PublishResult,
|
||||||
// ── Zod schemas ────────────────────────────────────────
|
} from "./publish.schemas";
|
||||||
|
import { publishModrinthVersion } from "./platforms/modrinth";
|
||||||
const PublishInputSchema = z.object({
|
import { publishCurseForgeVersion } from "./platforms/curseforge";
|
||||||
configName: z.string(),
|
import type { PlatformContext } from "./platforms/types";
|
||||||
version: z.string(),
|
|
||||||
mcVersions: z.object({
|
|
||||||
modrinth: z.array(z.object({ mc_version: z.string() })),
|
|
||||||
curseforge: z.array(z.object({ mc_version: z.string() })),
|
|
||||||
}),
|
|
||||||
changelog: z.string(),
|
|
||||||
versionType: z.enum(["release", "beta", "alpha"]),
|
|
||||||
});
|
|
||||||
|
|
||||||
const ProgressEventSchema = z.object({
|
|
||||||
platform: z.enum(["modrinth", "curseforge"]),
|
|
||||||
current: z.number().int().min(0),
|
|
||||||
total: z.number().int().min(0),
|
|
||||||
status: z.enum(["running", "completed", "failed"]),
|
|
||||||
errors: z.array(z.string()),
|
|
||||||
});
|
|
||||||
|
|
||||||
const PlatformResultSchema = z.object({
|
|
||||||
success: z.number().int().min(0),
|
|
||||||
fail: z.number().int().min(0),
|
|
||||||
errors: z.array(z.string()),
|
|
||||||
});
|
|
||||||
|
|
||||||
const PublishResultSchema = z.object({
|
|
||||||
modrinth: PlatformResultSchema,
|
|
||||||
curseforge: PlatformResultSchema,
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── Inferred types ─────────────────────────────────────
|
|
||||||
|
|
||||||
type PublishInput = z.infer<typeof PublishInputSchema>;
|
|
||||||
type ProgressEvent = z.infer<typeof ProgressEventSchema>;
|
|
||||||
type PlatformResult = z.infer<typeof PlatformResultSchema>;
|
|
||||||
type PublishResult = z.infer<typeof PublishResultSchema>;
|
|
||||||
|
|
||||||
// ── tRPC init ──────────────────────────────────────────
|
// ── tRPC init ──────────────────────────────────────────
|
||||||
|
|
||||||
@ -57,149 +20,7 @@ const t = initTRPC.create();
|
|||||||
|
|
||||||
const ee = new EventEmitter();
|
const ee = new EventEmitter();
|
||||||
|
|
||||||
// ── Helpers ────────────────────────────────────────────
|
// ── Platform orchestrator ──────────────────────────────
|
||||||
|
|
||||||
function resolveVersionName(
|
|
||||||
template: string,
|
|
||||||
templates: Record<string, string> | undefined,
|
|
||||||
values: Record<string, string>,
|
|
||||||
): string {
|
|
||||||
const tmpl = new Template(template, templates);
|
|
||||||
return tmpl.format(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildJarPath(
|
|
||||||
projectDir: string,
|
|
||||||
format: string,
|
|
||||||
version: string,
|
|
||||||
mcVersion: string,
|
|
||||||
): string {
|
|
||||||
const tmpl = new Template(format);
|
|
||||||
const filename = tmpl.format({ version, mc_version: mcVersion });
|
|
||||||
return path.join(projectDir, "build", "libs", filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
function readJarBlob(filePath: string): Blob {
|
|
||||||
try {
|
|
||||||
const buffer = readFileSync(filePath);
|
|
||||||
return new Blob([buffer]);
|
|
||||||
} catch (err) {
|
|
||||||
const msg = err instanceof Error ? err.message : String(err);
|
|
||||||
throw new Error(`Failed to read file "${filePath}": ${msg}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function errorMessage(err: unknown): string {
|
|
||||||
return err instanceof Error ? err.message : String(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Platform publish logic ─────────────────────────────
|
|
||||||
|
|
||||||
interface PlatformContext {
|
|
||||||
input: PublishInput;
|
|
||||||
config: Awaited<ReturnType<typeof getConfig>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function publishModrinthVersion(
|
|
||||||
ctx: PlatformContext,
|
|
||||||
mcVersion: string,
|
|
||||||
): Promise<void> {
|
|
||||||
const { input, config } = ctx;
|
|
||||||
|
|
||||||
const versionNumber = resolveVersionName(
|
|
||||||
config.modrinth.version,
|
|
||||||
undefined,
|
|
||||||
{ version: input.version, mc_version: mcVersion },
|
|
||||||
);
|
|
||||||
|
|
||||||
const versionName = resolveVersionName(
|
|
||||||
config.modrinth.version_name,
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
version: input.version,
|
|
||||||
mc_version: mcVersion,
|
|
||||||
mc_version_range: mcVersion,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const primaryPath = buildJarPath(
|
|
||||||
config.project_dir,
|
|
||||||
config.filename_format,
|
|
||||||
input.version,
|
|
||||||
mcVersion,
|
|
||||||
);
|
|
||||||
const sourcePath = buildJarPath(
|
|
||||||
config.project_dir,
|
|
||||||
config.source_filename_format,
|
|
||||||
input.version,
|
|
||||||
mcVersion,
|
|
||||||
);
|
|
||||||
|
|
||||||
const primaryBlob = readJarBlob(primaryPath);
|
|
||||||
const sourceBlob = readJarBlob(sourcePath);
|
|
||||||
|
|
||||||
const data: CreatableVersion = {
|
|
||||||
project_id: config.modrinth.project_id,
|
|
||||||
name: versionName,
|
|
||||||
version_number: versionNumber,
|
|
||||||
game_versions: [mcVersion],
|
|
||||||
version_type: input.versionType,
|
|
||||||
loaders: config.modrinth.loaders,
|
|
||||||
featured: false,
|
|
||||||
dependencies: config.modrinth.dependencies.map((d) => ({
|
|
||||||
version_id: null,
|
|
||||||
project_id: d.project_id,
|
|
||||||
file_name: null,
|
|
||||||
dependency_type: d.dependency_type,
|
|
||||||
})),
|
|
||||||
file_parts: ["primary", "sources"],
|
|
||||||
primary_file: "primary",
|
|
||||||
file_types: { sources: "sources-jar" },
|
|
||||||
environment: config.modrinth.environment,
|
|
||||||
};
|
|
||||||
|
|
||||||
await createVersion(modrinthClient, data, {
|
|
||||||
primary: primaryBlob,
|
|
||||||
sources: sourceBlob,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function publishCurseForgeVersion(
|
|
||||||
ctx: PlatformContext,
|
|
||||||
mcVersion: string,
|
|
||||||
): Promise<void> {
|
|
||||||
const { input, config } = ctx;
|
|
||||||
|
|
||||||
const displayName = resolveVersionName(
|
|
||||||
config.curseforge.version_name,
|
|
||||||
{ filename_format: config.filename_format },
|
|
||||||
{ version: input.version, mc_version: mcVersion },
|
|
||||||
);
|
|
||||||
|
|
||||||
const primaryPath = buildJarPath(
|
|
||||||
config.project_dir,
|
|
||||||
config.filename_format,
|
|
||||||
input.version,
|
|
||||||
mcVersion,
|
|
||||||
);
|
|
||||||
|
|
||||||
const primaryBlob = readJarBlob(primaryPath);
|
|
||||||
|
|
||||||
const metadata: UploadMetadata = {
|
|
||||||
changelog: input.changelog,
|
|
||||||
changelogType: "markdown",
|
|
||||||
displayName,
|
|
||||||
releaseType: input.versionType,
|
|
||||||
relations: config.curseforge.relations,
|
|
||||||
};
|
|
||||||
|
|
||||||
await uploadFile(
|
|
||||||
curseforgeClient,
|
|
||||||
config.curseforge.project_id,
|
|
||||||
metadata,
|
|
||||||
primaryBlob,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function runPlatform(
|
async function runPlatform(
|
||||||
platform: "modrinth" | "curseforge",
|
platform: "modrinth" | "curseforge",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user