Add validation for Modrinth and CurseForge version fields
This commit is contained in:
parent
f1447a3b91
commit
80d664936a
@ -26,6 +26,8 @@ import {
|
|||||||
checkSourceFiles,
|
checkSourceFiles,
|
||||||
lookupModrinthProject,
|
lookupModrinthProject,
|
||||||
lookupCurseforgeProject,
|
lookupCurseforgeProject,
|
||||||
|
fetchModrinthVersionNames,
|
||||||
|
fetchCurseforgeFileNames,
|
||||||
} from "@/services/project";
|
} from "@/services/project";
|
||||||
import {
|
import {
|
||||||
useFieldValidations,
|
useFieldValidations,
|
||||||
@ -37,6 +39,32 @@ import {
|
|||||||
type ListValidationDef,
|
type ListValidationDef,
|
||||||
} from "@/hooks/useFieldValidations";
|
} from "@/hooks/useFieldValidations";
|
||||||
import type { Config, DependencyType, UploadRelationType } from "@/types";
|
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 推导)
|
// 表单路径类型(从 Config 推导)
|
||||||
@ -169,6 +197,62 @@ const STATIC_DEFS: StaticValidationDef<Config>[] = [
|
|||||||
: { status: "error", help: r.error };
|
: { 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>[] = [
|
const LIST_DEFS: ListValidationDef<Config>[] = [
|
||||||
@ -565,23 +649,23 @@ function ConfigModalForm({
|
|||||||
<Input placeholder="8 位 base62" />
|
<Input placeholder="8 位 base62" />
|
||||||
</ValidatedFormItem>
|
</ValidatedFormItem>
|
||||||
|
|
||||||
<Form.Item
|
<ValidatedFormItem
|
||||||
name={["modrinth", "version_name"]}
|
name={["modrinth", "version_name"]}
|
||||||
label="版本名称模板"
|
label="版本名称模板"
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
extra="支持 ${version}、${mc_version_range} 等占位符"
|
extra="支持 ${version}、${mc_version_range} 等占位符"
|
||||||
>
|
>
|
||||||
<Input placeholder="ModName v${version} for Minecraft ${mc_version_range}" />
|
<Input placeholder="ModName v${version} for Minecraft ${mc_version_range}" />
|
||||||
</Form.Item>
|
</ValidatedFormItem>
|
||||||
|
|
||||||
<Form.Item
|
<ValidatedFormItem
|
||||||
name={["modrinth", "version"]}
|
name={["modrinth", "version"]}
|
||||||
label="版本号模板"
|
label="版本号模板"
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
extra="支持 ${version}、${mc_version} 等占位符"
|
extra="支持 ${version}、${mc_version} 等占位符"
|
||||||
>
|
>
|
||||||
<Input placeholder="${version}-mc${mc_version}" />
|
<Input placeholder="${version}-mc${mc_version}" />
|
||||||
</Form.Item>
|
</ValidatedFormItem>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["modrinth", "loaders"]}
|
name={["modrinth", "loaders"]}
|
||||||
@ -696,14 +780,14 @@ function ConfigModalForm({
|
|||||||
/>
|
/>
|
||||||
</ValidatedFormItem>
|
</ValidatedFormItem>
|
||||||
|
|
||||||
<Form.Item
|
<ValidatedFormItem
|
||||||
name={["curseforge", "version_name"]}
|
name={["curseforge", "version_name"]}
|
||||||
label="版本名称模板"
|
label="版本名称模板"
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
extra="支持 #{filename_format} 等占位符"
|
extra="支持 #{filename_format} 等占位符"
|
||||||
>
|
>
|
||||||
<Input placeholder="#{filename_format}" />
|
<Input placeholder="#{filename_format}" />
|
||||||
</Form.Item>
|
</ValidatedFormItem>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["curseforge", "environment"]}
|
name={["curseforge", "environment"]}
|
||||||
|
|||||||
@ -257,6 +257,8 @@ export async function checkSourceFiles(
|
|||||||
|
|
||||||
import { getProject } from "@/lib/modrinth/project";
|
import { getProject } from "@/lib/modrinth/project";
|
||||||
import { getMod } from "@/lib/curseforge/mod";
|
import { getMod } from "@/lib/curseforge/mod";
|
||||||
|
import { listVersions } from "@/lib/modrinth";
|
||||||
|
import { getFiles } from "@/lib/curseforge";
|
||||||
import { modrinthClient, curseforgeClient } from "./clients";
|
import { modrinthClient, curseforgeClient } from "./clients";
|
||||||
|
|
||||||
export async function lookupModrinthProject(
|
export async function lookupModrinthProject(
|
||||||
@ -280,3 +282,43 @@ export async function lookupCurseforgeProject(
|
|||||||
return { ok: false, error: err instanceof Error ? err.message : "查询 CurseForge 项目失败" };
|
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),
|
modules: z.array(FileModuleSchema),
|
||||||
}).pick({
|
}).pick({
|
||||||
|
displayName: true,
|
||||||
fileName: true,
|
fileName: true,
|
||||||
});
|
});
|
||||||
export type CurseForgeFile = z.infer<typeof CurseForgeFileSchema>;
|
export type CurseForgeFile = z.infer<typeof CurseForgeFileSchema>;
|
||||||
|
|||||||
@ -114,6 +114,7 @@ export const VersionSchema = BaseVersionSchema.extend({
|
|||||||
/** 该版本的所有文件列表 */
|
/** 该版本的所有文件列表 */
|
||||||
files: z.array(VersionFileSchema),
|
files: z.array(VersionFileSchema),
|
||||||
}).pick({
|
}).pick({
|
||||||
|
name: true,
|
||||||
version_number: true,
|
version_number: true,
|
||||||
});
|
});
|
||||||
export type Version = z.infer<typeof VersionSchema>;
|
export type Version = z.infer<typeof VersionSchema>;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user