ci(build): 增加第三阶段全量门禁与验收记录

This commit is contained in:
2026-09-07 16:57:36 +08:00
parent 508f13e5d8
commit 941f51d457
8 changed files with 195 additions and 11 deletions
+40
View File
@@ -0,0 +1,40 @@
"""检查仓库 Markdown 相对链接;忽略远程 URL、页内锚点与示例占位。"""
from __future__ import annotations
import re
from pathlib import Path
from urllib.parse import unquote
ROOT = Path(__file__).resolve().parents[1]
LINK = re.compile(r"(?<!!)\[[^\]]*\]\(([^)]+)\)")
def main() -> int:
failures: list[str] = []
for document in ROOT.rglob("*.md"):
if any(part in {".build", ".venv", "node_modules", "target"} for part in document.parts):
continue
source = document.read_text(encoding="utf-8")
for raw in LINK.findall(source):
target = raw.strip().strip("<>").split()[0]
if not target or target.startswith(("#", "http://", "https://", "mailto:")):
continue
path = unquote(target.split("#", 1)[0])
resolved = (document.parent / path).resolve()
try:
resolved.relative_to(ROOT)
except ValueError:
failures.append(f"{document.relative_to(ROOT)}: 链接越出仓库: {target}")
continue
if not resolved.exists():
failures.append(f"{document.relative_to(ROOT)}: 不存在: {target}")
if failures:
print("\n".join(failures))
return 1
print("Markdown 相对链接检查通过")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+61
View File
@@ -0,0 +1,61 @@
"""在临时目录启动三个 ASGI 应用,不读取仓库 data 或调用外部服务。"""
from __future__ import annotations
import os
import subprocess
import sys
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def main() -> int:
with tempfile.TemporaryDirectory(prefix="notesagent-phase3-") as temporary:
root = Path(temporary)
os.environ.update({
"APP_DATA_DIR": str(root / "backend"),
"APP_DB_PATH": str(root / "backend" / "app.db"),
"APP_VAULT_PATH": str(root / "vault"),
"APP_ATTACHMENTS_PATH": str(root / "backend" / "attachments"),
})
sys.path.insert(0, str(ROOT / "backend"))
from fastapi.testclient import TestClient
from app.main import app as core
with TestClient(core) as client:
assert client.get("/health").json()["status"] == "ok"
assert client.get("/api/status").status_code == 200
def project_python(project: Path) -> Path:
candidates = [project / ".venv" / "Scripts" / "python.exe", project / ".venv" / "bin" / "python"]
return next(path for path in candidates if path.exists())
sync_code = """from pathlib import Path
from fastapi.testclient import TestClient
from sync_server.app import create_app
from sync_server.database import Database
from sync_server.storage import DiskObjects
root=Path(r'%s')
with TestClient(create_app(Database('sqlite:///'+str(root/'sync.db')),DiskObjects(root/'objects'),root/'staging')) as c:
assert c.get('/ready').json()=={'status':'ready','schema':1}
assert c.get('/sync/v1/handshake').json()['protocol']==1
""" % root
subprocess.run([project_python(ROOT / "server sync"), "-c", sync_code], cwd=ROOT / "server sync", check=True)
community_code = """from pathlib import Path
from fastapi.testclient import TestClient
from community.app import Registry,create_app
root=Path(r'%s')
with TestClient(create_app(Registry(root/'community.db'))) as c:
assert c.get('/health').json()['status']=='ok'
assert c.get('/catalog/v1/sources').json()['schema_version']==1
""" % root
subprocess.run([project_python(ROOT / "community-server"), "-c", community_code], cwd=ROOT / "community-server", check=True)
print("AI Core、Sync v1、Community v1 隔离冒烟通过")
return 0
if __name__ == "__main__":
raise SystemExit(main())