Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions configs-default/pack-update.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
{
"_comment": "世界包更新。apiKey 来自 https://console.curseforge.com/(x-api-key)。Bedrock gameId=78022,Addons classId=4984。官方 /mods/search 若 403 会自动改用 searchBaseUrl(api.curse.tools)。也可用环境变量 CURSEFORGE_API_KEY。defaultBindingEnabled=false 时新建绑定默认关闭,需手改 enabled 或改此默认后才会自动检查/更新。",
"_comment": "世界包更新。apiKey 来自 https://console.curseforge.com/(x-api-key)。Bedrock gameId=78022,Addons classId=4984。官方 /mods/search 若 403 会自动改用 searchBaseUrl(api.curse.tools)。也可用环境变量 CURSEFORGE_API_KEY。match 为源无关策略,置于顶层。defaultBindingEnabled=false 时新建绑定默认关闭,需手改 enabled 或改此默认后才会自动检查/更新。",
"enabled": true,
"checkOnBdsStart": true,
"applyOnBdsStart": true,
"askConfirmOnBind": true,
"probeSourceAfterInstall": true,
"defaultBindingEnabled": false,
"match": {
"nameMinScore": 0.6,
"stripFolderTags": true
},
"providers": {
"curseforge": {
"enabled": true,
Expand All @@ -15,11 +19,7 @@
"gameId": 78022,
"classId": 4984,
"pageSize": 10,
"preferredReleaseTypes": ["release", "beta", "alpha"],
"match": {
"nameMinScore": 0.6,
"stripFolderTags": true
}
"preferredReleaseTypes": ["release", "beta", "alpha"]
}
},
"versionPolicy": {
Expand Down
16 changes: 9 additions & 7 deletions docs/guide/pack-update.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ flowchart TD
"applyOnBdsStart": true,
"askConfirmOnBind": true,
"probeSourceAfterInstall": true,
"defaultBindingEnabled": false,
"match": {
"nameMinScore": 0.6,
"stripFolderTags": true
},
"providers": {
"curseforge": {
"enabled": true,
Expand All @@ -97,11 +102,7 @@ flowchart TD
"gameId": 78022,
"classId": 4984,
"pageSize": 10,
"preferredReleaseTypes": ["release", "beta", "alpha"],
"match": {
"nameMinScore": 0.6,
"stripFolderTags": true
}
"preferredReleaseTypes": ["release", "beta", "alpha"]
}
},
"versionPolicy": {
Expand All @@ -122,12 +123,13 @@ flowchart TD

| 字段 | 含义 |
|------|------|
| `defaultBindingEnabled` | 新建绑定的默认 `enabled`;`false` 时仍写入 `pack-sources.json`,但不会自动检查/更新。 |
| `match.nameMinScore` | 安装后自动绑定的最低相似度阈值(源无关,顶层)。 |
| `match.stripFolderTags` | 清洗时是否去掉方括号标签(如 `[BP]`/`[玩法]`)。 |
| `gameId` | **Minecraft Bedrock = `78022`**。历史误用 `459` 无效,加载时会纠正。Java Minecraft 是 `432`,不要混用。 |
| `classId` | Bedrock **Addons = `4984`**;`null` 时用 `/v1/categories?classesOnly=true` 解析「Addons」。 |
| `baseUrl` | 官方 Core API:getMod / files / download-url。 |
| `searchBaseUrl` | 搜索镜像;官方 search 403 时回退。 |
| `nameMinScore` | 安装后自动绑定的最低相似度阈值。 |
| `stripFolderTags` | 清洗时是否去掉方括号标签(如 `[BP]`/`[玩法]`)。 |

### 3.4 绑定文件示例

Expand Down
18 changes: 0 additions & 18 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"@minecraft/server-net": "1.0.0-beta.11940b24",
"@minecraft/server-ui": "2.2.0-beta.1.26.40-preview.30",
"@minecraft/vanilla-data": "1.26.40-preview.30",
"@types/cli-progress": "^3.11.6",
"@types/lodash": "^4.17.24",
"@types/node": "^26.1.1",
"@types/ws": "^8.18.1",
Expand Down
55 changes: 43 additions & 12 deletions sfmc/src/pack-update/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
import fs from "node:fs";
import path from "node:path";
import { ROOT, resolveDefaultsDir, seedMissingConfigsFromDefaults } from "../runtime.js";
import type { PackUpdateConfig, PackUpdateMatchConfig } from "./types.js";
import type { CurseForgeProviderConfig, PackUpdateConfig, PackUpdateMatchConfig } from "./types.js";

const DEFAULT_MATCH: PackUpdateMatchConfig = {
nameMinScore: 0.6,
stripFolderTags: true,
};

const DEFAULTS: PackUpdateConfig = {
enabled: true,
Expand All @@ -14,6 +19,7 @@ const DEFAULTS: PackUpdateConfig = {
probeSourceAfterInstall: true,
/* 探测绑定默认关闭,避免误更新;可在 pack-update.json 改为 true */
defaultBindingEnabled: false,
match: { ...DEFAULT_MATCH },
providers: {
curseforge: {
enabled: true,
Expand All @@ -24,10 +30,6 @@ const DEFAULTS: PackUpdateConfig = {
classId: 4984,
pageSize: 10,
preferredReleaseTypes: ["release", "beta", "alpha"],
match: {
nameMinScore: 0.6,
stripFolderTags: true,
},
},
},
versionPolicy: {
Expand Down Expand Up @@ -64,12 +66,34 @@ export function packUpdateConfigPath(): string {
return path.join(ROOT, "configs", "pack-update.json");
}

/**
* 将旧版挂在 providers.curseforge.match 的字段提升到顶层 match(兼容一版)。
* 顶层显式配置优先于嵌套遗留。
*/
function hoistLegacyMatch(raw: Record<string, unknown>): PackUpdateMatchConfig | undefined {
const top = raw.match;
const providers = raw.providers as Record<string, unknown> | undefined;
const cf = providers?.curseforge as Record<string, unknown> | undefined;
const nested = cf?.match;
const pick =
top && typeof top === "object" && !Array.isArray(top)
? (top as Partial<PackUpdateMatchConfig>)
: nested && typeof nested === "object" && !Array.isArray(nested)
? (nested as Partial<PackUpdateMatchConfig>)
: undefined;
if (!pick) return undefined;
return deepMerge(
DEFAULT_MATCH as unknown as Record<string, unknown>,
pick as Record<string, unknown>
) as unknown as PackUpdateMatchConfig;
}

export function loadPackUpdateConfig(): PackUpdateConfig {
const cfgPath = packUpdateConfigPath();
let raw: Partial<PackUpdateConfig> = {};
let raw: Record<string, unknown> = {};
if (fs.existsSync(cfgPath)) {
try {
raw = JSON.parse(fs.readFileSync(cfgPath, "utf8")) as Partial<PackUpdateConfig>;
raw = JSON.parse(fs.readFileSync(cfgPath, "utf8")) as Record<string, unknown>;
} catch {
raw = {};
}
Expand All @@ -78,34 +102,41 @@ export function loadPackUpdateConfig(): PackUpdateConfig {
const bundled = defaultsDir ? path.join(defaultsDir, "pack-update.json") : "";
if (bundled && fs.existsSync(bundled)) {
try {
raw = JSON.parse(fs.readFileSync(bundled, "utf8")) as Partial<PackUpdateConfig>;
raw = JSON.parse(fs.readFileSync(bundled, "utf8")) as Record<string, unknown>;
} catch {
raw = {};
}
}
}

const legacyMatch = hoistLegacyMatch(raw);
const merged = deepMerge(
DEFAULTS as unknown as Record<string, unknown>,
raw as Record<string, unknown>
) as unknown as PackUpdateConfig;

if (legacyMatch) {
merged.match = legacyMatch;
}

const envKey = process.env.CURSEFORGE_API_KEY?.trim();
if (envKey) {
merged.providers.curseforge.apiKey = envKey;
}

/* 兼容旧配置里未接线的 byUuidInArchive/byName,避免 deepMerge 残留脏字段影响契约 */
const match = merged.providers.curseforge.match as PackUpdateMatchConfig & Record<string, unknown>;
/* 剥离曾挂在 CF 下的 match / 未接线死字段,避免脏配置渗入 Provider(Demeter/契约) */
const cf = merged.providers.curseforge as CurseForgeProviderConfig & Record<string, unknown>;
delete cf.match;
const match = merged.match as PackUpdateMatchConfig & Record<string, unknown>;
delete match.byUuidInArchive;
delete match.byName;

return merged;
}

/** 匹配策略访问器:编排层勿直接挖 providers.curseforge.match(Demeter) */
/** 匹配策略访问器:编排层只读顶层 match,勿挖 providers.*(Demeter) */
export function getPackMatchConfig(cfg: PackUpdateConfig): PackUpdateMatchConfig {
return cfg.providers.curseforge.match;
return cfg.match;
}

/**
Expand Down
19 changes: 14 additions & 5 deletions sfmc/src/pack-update/providers/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
/**
* 源提供者注册表:按配置构造 PackSourceProvider(OCP:新源加分支/注册,不改编排层)。
* 源提供者注册表:按 id 构造 PackSourceProvider(OCP:新源加 case,不改编排层)。
*/
import type { PackSourceProvider, PackUpdateConfig } from "../types.js";
import type { PackProviderId, PackSourceProvider, PackUpdateConfig } from "../types.js";
import { CurseForgeBedrockProvider } from "./curseforge.js";

export function createPackSourceProvider(cfg: PackUpdateConfig): PackSourceProvider {
/* 当前仅 curseforge;后续源在此扩展,service 只依赖 PackSourceProvider */
return new CurseForgeBedrockProvider(cfg.providers.curseforge);
export function createPackSourceProvider(
cfg: PackUpdateConfig,
id: PackProviderId = "curseforge"
): PackSourceProvider {
switch (id) {
case "curseforge":
return new CurseForgeBedrockProvider(cfg.providers.curseforge);
default: {
const _exhaustive: never = id;
throw new Error(`未知 pack 源提供者: ${String(_exhaustive)}`);
}
}
}

export { CurseForgeBedrockProvider };
Loading