feat(deployer): skip sync on localhost and complete skill READMEs

Redeploy locally with docker compose when the node is 127.0.0.1 and
the image already exists. Make list/sync/remote/deb importable under
python3 -I -S. Add human README.md for every builtin skill, slim ack's
user-facing docs, and drop unused OpenSpec config.
This commit is contained in:
2026-09-04 00:07:15 +08:00
parent ef22c6829e
commit c2d6a5a0d7
22 changed files with 517 additions and 372 deletions
+43 -2
View File
@@ -2,6 +2,8 @@ from __future__ import annotations
import importlib.util
import os
import subprocess
import sys
import tempfile
import unittest
from io import StringIO
@@ -10,8 +12,10 @@ from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[1]
CHECK_PY = REPO_ROOT / "skills" / "deployer" / "scripts" / "deploy" / "check.py"
LIB_PY = REPO_ROOT / "skills" / "deployer" / "scripts" / "deploy" / "lib.py"
DEPLOY_SCRIPTS = REPO_ROOT / "skills" / "deployer" / "scripts" / "deploy"
CHECK_PY = DEPLOY_SCRIPTS / "check.py"
LIST_PY = DEPLOY_SCRIPTS / "list.py"
LIB_PY = DEPLOY_SCRIPTS / "lib.py"
lib_spec = importlib.util.spec_from_file_location("deployer_lib", LIB_PY)
assert lib_spec is not None and lib_spec.loader is not None
@@ -55,6 +59,43 @@ class DeployerCheckTests(unittest.TestCase):
self.assertIn("scripts/deploy/check.py", skill)
self.assertIn("不要写假 node", skill)
def test_skill_documents_redeploy_happy_path(self) -> None:
skill = (REPO_ROOT / "skills" / "deployer" / "SKILL.md").read_text(
encoding="utf-8"
)
compose_section = skill.split("## Compose 操作", 1)[1].split("## Argo CD", 1)[0]
self.assertIn("不要读 compose.md", compose_section)
self.assertIn("remote.py <svc> recreate", compose_section)
self.assertIn("一句问", compose_section)
self.assertIn("没有 `test` 就用非 `prod` 的那个", compose_section)
self.assertIn("不要** `sync.py` / `remote.py`", compose_section)
self.assertIn("docker compose up -d --force-recreate", compose_section)
self.assertNotIn(
"读 [compose.md](references/compose.md)。用 `list.py` 摸底",
compose_section,
)
def test_list_py_isolated_mode_imports_lib(self) -> None:
with tempfile.TemporaryDirectory() as temp:
project = Path(temp)
root = project / ".pouch" / "deployer"
env = root / "test"
env.mkdir(parents=True)
(root / "_config.yaml").write_text("node: my-vps\n", encoding="utf-8")
(env / "compose.yaml").write_text(
"services:\n web:\n image: nginx\n", encoding="utf-8"
)
proc = subprocess.run(
[sys.executable, "-I", "-S", str(LIST_PY)],
cwd=str(project),
capture_output=True,
text=True,
check=False,
)
self.assertEqual(proc.returncode, 0, proc.stderr + proc.stdout)
self.assertIn("test", proc.stdout)
self.assertNotIn("No module named 'lib'", proc.stderr)
def test_missing_layout_fails(self) -> None:
with tempfile.TemporaryDirectory() as temp:
project = Path(temp)