Add validation for Modrinth and CurseForge version fields
This commit is contained in:
parent
f1447a3b91
commit
80d664936a
@ -26,6 +26,8 @@ import {
|
||||
checkSourceFiles,
|
||||
lookupModrinthProject,
|
||||
lookupCurseforgeProject,
|
||||
fetchModrinthVersionNames,
|
||||
fetchCurseforgeFileNames,
|
||||
} from "@/services/project";
|
||||
import {
|
||||
useFieldValidations,
|
||||
@ -37,6 +39,32 @@ import {
|
||||
type ListValidationDef,
|
||||
} from "@/hooks/useFieldValidations";
|
||||
import type { Config, DependencyType, UploadRelationType } from "@/types";
|
||||
import { Template } from "@/lib/utils/template";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 工具
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function parseAndCount(source: string, items: string[]): string[] {
|
||||
const matched: string[] = [];
|
||||
try {
|
||||
const tmpl = new Template(source);
|
||||
for (const item of items) {
|
||||
try { tmpl.parse(item); matched.push(item); } catch { /* skip */ }
|
||||
}
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
function parseAndCountWithTmpl(tmpl: Template, items: string[]): string[] {
|
||||
const matched: string[] = [];
|
||||
for (const item of items) {
|
||||
try { tmpl.parse(item); matched.push(item); } catch { /* skip */ }
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 表单路径类型(从 Config 推导)
|
||||
@ -169,6 +197,62 @@ const STATIC_DEFS: StaticValidationDef<Config>[] = [
|
||||
: { status: "error", help: r.error };
|
||||
},
|
||||
},
|
||||
// 7. Modrinth 版本名称模板
|
||||
{
|
||||
target: ["modrinth", "version_name"],
|
||||
deps: [["modrinth", "project_id"], ["modrinth", "version_name"]],
|
||||
async validate(getVal) {
|
||||
const pid = getVal(["modrinth", "project_id"]) as string | undefined;
|
||||
const source = getVal(["modrinth", "version_name"]) as string | undefined;
|
||||
if (!pid || !source) return null;
|
||||
const r = await fetchModrinthVersionNames(pid);
|
||||
if (!r.ok) return { status: "error", help: r.error };
|
||||
const matched = parseAndCount(source, r.names);
|
||||
if (matched.length > 0) {
|
||||
return { status: "success", help: `匹配到 ${matched.length} 个版本` };
|
||||
}
|
||||
const examples = r.names.slice(0, 3).join(",");
|
||||
return { status: "error", help: `未匹配到任何版本,示例:${examples}` };
|
||||
},
|
||||
},
|
||||
// 8. Modrinth 版本号模板
|
||||
{
|
||||
target: ["modrinth", "version"],
|
||||
deps: [["modrinth", "project_id"], ["modrinth", "version"]],
|
||||
async validate(getVal) {
|
||||
const pid = getVal(["modrinth", "project_id"]) as string | undefined;
|
||||
const source = getVal(["modrinth", "version"]) as string | undefined;
|
||||
if (!pid || !source) return null;
|
||||
const r = await fetchModrinthVersionNames(pid);
|
||||
if (!r.ok) return { status: "error", help: r.error };
|
||||
const matched = parseAndCount(source, r.numbers);
|
||||
if (matched.length > 0) {
|
||||
return { status: "success", help: `匹配到 ${matched.length} 个版本` };
|
||||
}
|
||||
const examples = r.numbers.slice(0, 3).join(",");
|
||||
return { status: "error", help: `未匹配到任何版本,示例:${examples}` };
|
||||
},
|
||||
},
|
||||
// 9. CurseForge 版本名称模板
|
||||
{
|
||||
target: ["curseforge", "version_name"],
|
||||
deps: [["curseforge", "project_id"], "filename_format", ["curseforge", "version_name"]],
|
||||
async validate(getVal) {
|
||||
const pid = getVal(["curseforge", "project_id"]) as number | undefined;
|
||||
const fmt = getVal("filename_format") as string | undefined;
|
||||
const source = getVal(["curseforge", "version_name"]) as string | undefined;
|
||||
if (pid == null || pid === 0 || !fmt || !source) return null;
|
||||
const r = await fetchCurseforgeFileNames(pid);
|
||||
if (!r.ok) return { status: "error", help: r.error };
|
||||
const tmpl = new Template(source, { filename_format: fmt });
|
||||
const matched = parseAndCountWithTmpl(tmpl, r.names);
|
||||
if (matched.length > 0) {
|
||||
return { status: "success", help: `匹配到 ${matched.length} 个版本` };
|
||||
}
|
||||
const examples = r.names.slice(0, 3).join(",");
|
||||
return { status: "error", help: `未匹配到任何版本,示例:${examples}` };
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const LIST_DEFS: ListValidationDef<Config>[] = [
|
||||
@ -565,23 +649,23 @@ function ConfigModalForm({
|
||||
<Input placeholder="8 位 base62" />
|
||||
</ValidatedFormItem>
|
||||
|
||||
<Form.Item
|
||||
<ValidatedFormItem
|
||||
name={["modrinth", "version_name"]}
|
||||
label="版本名称模板"
|
||||
rules={[{ required: true }]}
|
||||
extra="支持 ${version}、${mc_version_range} 等占位符"
|
||||
>
|
||||
<Input placeholder="ModName v${version} for Minecraft ${mc_version_range}" />
|
||||
</Form.Item>
|
||||
</ValidatedFormItem>
|
||||
|
||||
<Form.Item
|
||||
<ValidatedFormItem
|
||||
name={["modrinth", "version"]}
|
||||
label="版本号模板"
|
||||
rules={[{ required: true }]}
|
||||
extra="支持 ${version}、${mc_version} 等占位符"
|
||||
>
|
||||
<Input placeholder="${version}-mc${mc_version}" />
|
||||
</Form.Item>
|
||||
</ValidatedFormItem>
|
||||
|
||||
<Form.Item
|
||||
name={["modrinth", "loaders"]}
|
||||
@ -696,14 +780,14 @@ function ConfigModalForm({
|
||||
/>
|
||||
</ValidatedFormItem>
|
||||
|
||||
<Form.Item
|
||||
<ValidatedFormItem
|
||||
name={["curseforge", "version_name"]}
|
||||
label="版本名称模板"
|
||||
rules={[{ required: true }]}
|
||||
extra="支持 #{filename_format} 等占位符"
|
||||
>
|
||||
<Input placeholder="#{filename_format}" />
|
||||
</Form.Item>
|
||||
</ValidatedFormItem>
|
||||
|
||||
<Form.Item
|
||||
name={["curseforge", "environment"]}
|
||||
|
||||
@ -257,6 +257,8 @@ export async function checkSourceFiles(
|
||||
|
||||
import { getProject } from "@/lib/modrinth/project";
|
||||
import { getMod } from "@/lib/curseforge/mod";
|
||||
import { listVersions } from "@/lib/modrinth";
|
||||
import { getFiles } from "@/lib/curseforge";
|
||||
import { modrinthClient, curseforgeClient } from "./clients";
|
||||
|
||||
export async function lookupModrinthProject(
|
||||
@ -280,3 +282,43 @@ export async function lookupCurseforgeProject(
|
||||
return { ok: false, error: err instanceof Error ? err.message : "查询 CurseForge 项目失败" };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Modrinth 项目的版本名和版本号列表。
|
||||
*/
|
||||
export async function fetchModrinthVersionNames(
|
||||
projectId: string,
|
||||
): Promise<OkResult<{ names: string[]; numbers: string[] }> | ErrResult> {
|
||||
try {
|
||||
const versions = await listVersions(modrinthClient, projectId, { include_changelog: false });
|
||||
return {
|
||||
ok: true,
|
||||
names: versions.map((v) => v.name),
|
||||
numbers: versions.map((v) => v.version_number),
|
||||
};
|
||||
} catch (err) {
|
||||
return { ok: false, error: err instanceof Error ? err.message : "获取 Modrinth 版本列表失败" };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 CurseForge 项目的文件 displayName 列表。
|
||||
*/
|
||||
export async function fetchCurseforgeFileNames(
|
||||
modId: number,
|
||||
): Promise<OkResult<{ names: string[] }> | ErrResult> {
|
||||
try {
|
||||
const all: string[] = [];
|
||||
let index = 0;
|
||||
const pageSize = 50;
|
||||
while (true) {
|
||||
const resp = await getFiles(curseforgeClient, { modId, index, pageSize });
|
||||
for (const f of resp.data) all.push(f.displayName);
|
||||
if (all.length >= resp.pagination.totalCount) break;
|
||||
index += pageSize;
|
||||
}
|
||||
return { ok: true, names: all };
|
||||
} catch (err) {
|
||||
return { ok: false, error: err instanceof Error ? err.message : "获取 CurseForge 文件列表失败" };
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,6 +172,7 @@ export const CurseForgeFileSchema = z.object({
|
||||
/** 文件模块列表 */
|
||||
modules: z.array(FileModuleSchema),
|
||||
}).pick({
|
||||
displayName: true,
|
||||
fileName: true,
|
||||
});
|
||||
export type CurseForgeFile = z.infer<typeof CurseForgeFileSchema>;
|
||||
|
||||
@ -114,6 +114,7 @@ export const VersionSchema = BaseVersionSchema.extend({
|
||||
/** 该版本的所有文件列表 */
|
||||
files: z.array(VersionFileSchema),
|
||||
}).pick({
|
||||
name: true,
|
||||
version_number: true,
|
||||
});
|
||||
export type Version = z.infer<typeof VersionSchema>;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user