Skip to content
Open
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
86 changes: 86 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# AGENTS.md — Metadata 仓库指引

面向自动化 Agent 与人类贡献者。产品决策细节见 [PLAN.md](./PLAN.md),使用方解析见 [README.md](./README.md)。

## 仓库职责

- **源数据**:`Metadata/**/*.xml` + 图片/Markdown/叶子 Manifest
- **核心库/CLI**:`Ra3.BattleNet.Metadata` — 展平、变量、XSD、语义校验、查询 API(**纯 managed,无 SkiaSharp**);**Desktop NuGet 只引此包**
- **Imaging CLI**:`Ra3.BattleNet.Metadata.Imaging` — 只转图并 stdout MD5,**不读 XML**;**不进主 NuGet**;`Build(convertImages:true)` / `build --webp` 在展平后按图调用
- **发布**:Cloudflare Pages 静态托管 `Output/`

## 常用命令

```bash
# 测试(MSTest)
dotnet test Metadata.sln

# 核心构建(默认;Desktop 本地调试同此)
dotnet run --project Ra3.BattleNet.Metadata -- build --src=./Metadata --dst=./Output

# 发布构建(核心 + WebP)
npm run build:release
# 或
bash build.sh --webp
```

- **不要**再引入 `RUN_STAGE_B` / `StageB` 控制面。
- 本地默认 **不** 跑 Imaging;CF:`wrangler.toml` → `bash build.sh --webp`。

## Schema

| 文件 | 校验对象 | 时机 |
|---|---|---|
| `Metadata/MetadataSchema.xsd` | 源树全部 `.xml` | 核心构建开始时硬失败 |
| `Metadata/MetadataPublishSchema.xsd` | 展平 `metadata.xml` | 变量替换后硬失败 |

实现:`SchemaValidator` + `MetadataBuilder.Build`。

改结构时:**同步改 XSD + README 属性清单 + 示例 + 测试**。

源树 `Base` / `InheritFrom` 仅构建期合并(`MetadataInheritance`);**发布物禁止**带继承痕迹。`Defines` 不做对象继承。

## 使用方(Desktop)解析要点

1. 只消费**展平** `metadata.xml` + 相对资源,不自己展开 Include。
2. `MetadataBuilder.Load(path|url)` → `Catalog` / `Applications` / `Mods`。
3. Package.Manifest / Icon / Changelog / Content 等是 **限定 ID**(`路径前缀:localId`),不是路径、也不是源树短名。
4. 登记节点 `Manifest[@Source]` 指向叶子清单 XML(含 File 表)。
5. 资源 URL = `BaseUrl` + `Source`;本地 = `BasePath` + `Source`。
6. 调试:本地 `Build` → 同一 `Load`;勿写死 `.png`。
7. 同名资源靠展平前缀隔离;勿在客户端再实现 public/private Include 语义。

完整步骤与属性表:README「使用方如何解析 Metadata」「模块属性清单」。

## 代码布局

```
Metadata/metadata.xml 入口
Metadata/Templates/ 脚手架(不 Include)
Metadata/** 分模块 Include 源
Ra3.BattleNet.Metadata/
MetadataBuilder.cs 核心 Build / Load
MetadataFlattener.cs Include 展平
MetadataInheritance.cs Base + InheritFrom 合并
SchemaValidator.cs XSD
VariableResolver.cs ${...}
Metadata.cs / MetadataParser.cs 树与加载
MetadataQueryExtensions.cs Catalog/Mods/...
Ra3.BattleNet.Metadata.Imaging/ WebP
Ra3.BattleNet.Metadata.Tests/ MSTest
```

## 修改约束

- FAILFAST:校验失败即停,不留半残 `Output`。
- 测试只许 **MSTest**,禁止 xunit。
- 主库禁止加入 SkiaSharp / 原生图片依赖。
- Manifest **权威在 Updater**;本仓示例 Hash 可为占位。
- 中文注释与文档;代码标识符 ASCII。
- git 访问外网仓库时用代理 `http://localhost:7890`(项目惯例)。

## 不要做的事

- 在 Desktop 仓以外擅自改 Desktop UI(联调另迭代)。
- 把 File 表内联进发布 `metadata.xml` 的 Manifest stub。
- 默认开启 WebP 或恢复 `StageB` 命名作为 API。
8 changes: 7 additions & 1 deletion Metadata.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@


Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.12.35514.174 d17.12
Expand All @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ra3.BattleNet.Metadata", "R
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ra3.BattleNet.Metadata.Tests", "Ra3.BattleNet.Metadata.Tests\Ra3.BattleNet.Metadata.Tests.csproj", "{F9429D39-9063-45C4-9EC5-84A34A4829D8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ra3.BattleNet.Metadata.Imaging", "Ra3.BattleNet.Metadata.Imaging\Ra3.BattleNet.Metadata.Imaging.csproj", "{A9429D39-9063-45C4-9EC5-84A34A4829D9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -21,6 +23,10 @@ Global
{F9429D39-9063-45C4-9EC5-84A34A4829D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F9429D39-9063-45C4-9EC5-84A34A4829D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F9429D39-9063-45C4-9EC5-84A34A4829D8}.Release|Any CPU.Build.0 = Release|Any CPU
{A9429D39-9063-45C4-9EC5-84A34A4829D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A9429D39-9063-45C4-9EC5-84A34A4829D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9429D39-9063-45C4-9EC5-84A34A4829D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A9429D39-9063-45C4-9EC5-84A34A4829D9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
150 changes: 150 additions & 0 deletions Metadata/MetadataPublishSchema.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 发布物 schema:展平后的单一 metadata.xml(无 Include)。核心构建对输出硬校验。 -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">

<xs:element name="Metadata" type="PublishRootType"/>

<xs:complexType name="PublishRootType">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="Tags" type="TagsType"/>
<xs:element name="Application" type="ApplicationType"/>
<xs:element name="Mod" type="ModType"/>
<xs:element name="Markdown" type="MarkdownRegType"/>
<xs:element name="Image" type="ImageRegType"/>
<xs:element name="Manifest" type="ManifestStubType"/>
</xs:choice>
<xs:attribute name="SchemaVersion" type="xs:string" use="required"/>
<xs:attribute name="ContentRevision" type="xs:string" use="required"/>
</xs:complexType>

<xs:complexType name="TagsType">
<xs:sequence>
<xs:element name="Commit" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="ApplicationType">
<xs:sequence>
<xs:element name="Version" type="xs:string" minOccurs="0"/>
<xs:element name="Packages" type="PackagesType" minOccurs="0"/>
<xs:element name="Posts" type="PostsType" minOccurs="0"/>
<xs:element name="Defines" type="DefinesType" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="ID" type="xs:string" use="required"/>
</xs:complexType>

<xs:complexType name="ModType">
<xs:sequence>
<xs:element name="CurrentVersion" type="xs:string" minOccurs="0"/>
<xs:element name="Icon" type="xs:string" minOccurs="0"/>
<xs:element name="Style" type="StyleType" minOccurs="0"/>
<xs:element name="Packages" type="PackagesType" minOccurs="0"/>
<xs:element name="Posts" type="PostsType" minOccurs="0"/>
<xs:element name="Defines" type="DefinesType" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="ID" type="xs:string" use="required"/>
</xs:complexType>

<xs:complexType name="StyleType">
<xs:sequence>
<xs:element name="Logo" type="LogoType" minOccurs="0"/>
<xs:element name="Controls" type="ControlsType" minOccurs="0"/>
<xs:element name="Background" type="BackgroundType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="LogoType" mixed="true">
<xs:attribute name="Width" type="xs:integer"/>
<xs:attribute name="Height" type="xs:integer"/>
</xs:complexType>

<xs:complexType name="ControlsType">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="BackgroundType">
<xs:sequence>
<xs:element name="Image" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="Random" type="xs:boolean"/>
</xs:complexType>

<xs:complexType name="PackagesType">
<xs:sequence>
<xs:element name="Package" type="PackageType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="PackageType">
<xs:sequence>
<xs:element name="ReleaseDate" type="xs:string" minOccurs="0"/>
<xs:element name="Changelogs" type="ChangelogsType" minOccurs="0"/>
<xs:element name="Manifest" type="xs:string" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="Version" type="xs:string" use="required"/>
</xs:complexType>

<xs:complexType name="ChangelogsType">
<xs:sequence>
<xs:element name="Changelog" type="LocalizedTextType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="LocalizedTextType" mixed="true">
<xs:attribute name="Language" type="xs:string" use="required"/>
</xs:complexType>

<xs:complexType name="PostsType">
<xs:sequence>
<xs:element name="Post" type="PostType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="PostType">
<xs:sequence>
<xs:element name="Titles" type="TitlesType"/>
<xs:element name="Contents" type="ContentsType"/>
</xs:sequence>
<xs:attribute name="DateTime" type="xs:string" use="required"/>
</xs:complexType>

<xs:complexType name="TitlesType">
<xs:sequence>
<xs:element name="Title" type="LocalizedTextType" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="ContentsType">
<xs:sequence>
<xs:element name="Content" type="LocalizedTextType" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="DefinesType">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="MarkdownRegType">
<xs:attribute name="ID" type="xs:string" use="required"/>
<xs:attribute name="Source" type="xs:string" use="required"/>
<xs:attribute name="Hash" type="xs:string"/>
</xs:complexType>

<xs:complexType name="ImageRegType">
<xs:attribute name="ID" type="xs:string" use="required"/>
<xs:attribute name="Source" type="xs:string"/>
<xs:attribute name="Url" type="xs:string"/>
<xs:attribute name="Hash" type="xs:string"/>
</xs:complexType>

<!-- 发布侧 stub:File 表在独立资源文件中,不内联 -->
<xs:complexType name="ManifestStubType">
<xs:attribute name="ID" type="xs:string" use="required"/>
<xs:attribute name="Source" type="xs:string" use="required"/>
</xs:complexType>

</xs:schema>
Loading