把项目从「手写 dist/」改成「wallpapers/ 是唯一真相来源,dist/ 由 pnpm build 生成」,
并补上配套的类型、门禁与抓取器。一次提交落地整条管线,因为拆开会留下不能构建的中间态。
- src/:运行时与模拟器源码(TS,strict),编译到 build/ 再拷进各分发
- tools/:build / dev / check-{syntax,paths,dist},以及抓取器与回归门禁 tools/checks/
(.scratch/ 下那批一次性脚本移入 tools/checks/ 并入库为长期门禁)
- wallpapers/:七档壁纸的源数据 + README.md(id/音频/预设的完整规范)
- docs/adr/0005-0008:构建管线与分发拓扑、模拟器契约、自包含 sim、每骨架资源布局
- .gitignore:排除 .scratch/ 的参考资料副本(上游 spine 整仓克隆 ~1.2 GB、
抓取侦查数据 ~680 MB)与调试转储;这些是本地调查材料,补偿会让仓库无法克隆
- 归一化 .gitignore/CONTEXT.md 行尾(工作区 CRLF、索引 LF 造成的整文件假 diff)
同时修掉三档卡住构建的未完工壁纸:
- kv45 的 meta.json 里 id 还是抓取期场景名 scene_main,经 downloader promote 正名为 kv45
- shajin / zhigengniao_juheye 的 meta.json 误用了骨架描述文件(name/spine/animations/pages)、
且都缺 preset.template.json;现按规范重建:骨架沉到 spines/<名>/(spine-ts 按 atlas 所在
目录解析贴图页)、补上元数据与单骨架预设,并清掉 zhigengniao 骨架里指向作者机的绝对路径
- 顺带 promote 已在 sources.yml 里的 kv46(月升之前,与兽共舞)
pnpm check 五道门全绿:10 个分发 / 7 档壁纸 / 141 处引用自包含。
78 lines
3.1 KiB
Python
78 lines
3.1 KiB
Python
"""补救:按**当前实际布局**重写各档壁纸预设里的资源路径。
|
||
|
||
背景:migrate-walls.py 的映射键在 Windows 下是反斜杠,而预设里是 `./effects/x.json` 这种正斜杠,
|
||
查表全落空 → 文件搬了、预设没改。这里不靠任何映射,直接**在壁纸目录里找**该文件现在在哪:
|
||
|
||
./effects/<名>.json → spines/<名>/<名>.json
|
||
./images/<…> → spines/<骨架>/<文件>(若该文件是某 atlas 声明的页或骨架自己的贴图)
|
||
否则 → scene/<文件>
|
||
./images/scene/<文件> → scene/<文件>
|
||
|
||
每个改写都要求目标**真实存在**,否则报错而不是写一个坏路径。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import re
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[1]
|
||
WALLS = ROOT / "wallpapers"
|
||
APPLY = "--apply" in sys.argv
|
||
PAGE_EXT = re.compile(r"\.(png|webp|jpg|jpeg)$", re.IGNORECASE)
|
||
|
||
|
||
def atlas_pages(text: str) -> list[str]:
|
||
return [l.strip() for l in text.splitlines() if l.strip() and ":" not in l.strip() and PAGE_EXT.search(l.strip())]
|
||
|
||
|
||
def owner_of(wall: Path, basename: str) -> str | None:
|
||
"""这个文件现在归哪个目录(spines/<名> 还是 scene)。"""
|
||
for atlas in (wall / "spines").rglob("*.atlas"):
|
||
if basename in atlas_pages(atlas.read_text(encoding="utf-8")):
|
||
return f"spines/{atlas.parent.name}"
|
||
for spine_dir in (wall / "spines").iterdir() if (wall / "spines").is_dir() else []:
|
||
if (spine_dir / basename).exists():
|
||
return f"spines/{spine_dir.name}"
|
||
if (wall / "scene" / basename).exists():
|
||
return "scene"
|
||
return None
|
||
|
||
|
||
failed: list[str] = []
|
||
for wall in sorted(p.parent for p in WALLS.rglob("preset.template.json")):
|
||
preset_path = wall / "preset.template.json"
|
||
preset = json.loads(preset_path.read_text(encoding="utf-8"))
|
||
changed: list[str] = []
|
||
|
||
def rewrite(value: object) -> object:
|
||
if isinstance(value, str) and value.startswith("./") and "/" in value[2:]:
|
||
basename = value.rsplit("/", 1)[-1]
|
||
owner = owner_of(wall, basename)
|
||
if owner and value != f"./{owner}/{basename}":
|
||
changed.append(f"{value} → ./{owner}/{basename}")
|
||
return f"./{owner}/{basename}"
|
||
if owner is None:
|
||
failed.append(f"{wall.relative_to(ROOT)}: {value} 在磁盘上找不到")
|
||
if isinstance(value, dict):
|
||
return {k: rewrite(v) for k, v in value.items()}
|
||
if isinstance(value, list):
|
||
return [rewrite(v) for v in value]
|
||
return value
|
||
|
||
new_preset = rewrite(preset)
|
||
print(f"\n== {wall.relative_to(ROOT)}")
|
||
for c in changed:
|
||
print(" " + c)
|
||
if APPLY and changed:
|
||
preset_path.write_text(json.dumps(new_preset, ensure_ascii=False, indent="\t") + "\n", encoding="utf-8")
|
||
|
||
if failed:
|
||
print("\n✗ 以下路径找不到对应文件(未改写):")
|
||
for f in failed:
|
||
print(" " + f)
|
||
sys.exit(1)
|
||
print(f"\n{'已重写' if APPLY else '将重写'} {len(changed) if False else ''}{'(dry-run)' if not APPLY else ''}")
|