Publish package metadata and Prettier settings through the scoped tsl-playbook bundle sync. Force-add only managed paths so the target branch ignore rules cannot drop the lockfile.
120 lines
3.9 KiB
Python
120 lines
3.9 KiB
Python
import re
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from scripts.build_tsl_playbook import build
|
|
|
|
|
|
ROOT_CONFIG_CONTENTS = {
|
|
"package.json": b'{"name":"fixture"}\n',
|
|
"package-lock.json": b'{"lockfileVersion":3}\n',
|
|
".prettierrc.json": b'{"proseWrap":"preserve"}\n',
|
|
".prettierignore": b"skills/thirdparty/**\n",
|
|
}
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SYNC_WORKFLOW = ROOT / ".gitea" / "workflows" / "sync-tsl-playbook.yml"
|
|
EXPECTED_MANAGED_PATHS = (
|
|
"AGENTS.md",
|
|
"package.json",
|
|
"package-lock.json",
|
|
".prettierrc.json",
|
|
".prettierignore",
|
|
"docs/tsl",
|
|
"skills/tsl-syntax-reference",
|
|
"skills/tsl-api-reference",
|
|
"tools/tsl-codegen",
|
|
)
|
|
|
|
|
|
class TslPlaybookSyncTests(unittest.TestCase):
|
|
def test_build_copies_root_config_files_verbatim(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
repo_root = Path(temp_dir) / "repo"
|
|
output = Path(temp_dir) / "bundle"
|
|
write_minimal_sources(repo_root)
|
|
|
|
build(output, repo_root)
|
|
|
|
for filename, expected in ROOT_CONFIG_CONTENTS.items():
|
|
with self.subTest(filename=filename):
|
|
self.assertEqual(expected, (output / filename).read_bytes())
|
|
|
|
def test_non_file_root_config_fails_before_output_cleanup(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
repo_root = Path(temp_dir) / "repo"
|
|
output = Path(temp_dir) / "bundle"
|
|
write_minimal_sources(repo_root)
|
|
|
|
invalid_config = repo_root / "package.json"
|
|
invalid_config.unlink()
|
|
invalid_config.mkdir()
|
|
output.mkdir()
|
|
sentinel = output / "keep.txt"
|
|
sentinel.write_text("keep\n", encoding="utf-8", newline="\n")
|
|
|
|
with self.assertRaisesRegex(
|
|
FileNotFoundError,
|
|
re.escape(str(invalid_config)),
|
|
):
|
|
build(output, repo_root)
|
|
|
|
self.assertEqual("keep\n", sentinel.read_text(encoding="utf-8"))
|
|
|
|
def test_root_config_files_are_not_ignored(self):
|
|
for filename in ROOT_CONFIG_CONTENTS:
|
|
with self.subTest(filename=filename):
|
|
self.assertTrue((ROOT / filename).is_file())
|
|
result = subprocess.run(
|
|
["git", "check-ignore", "--quiet", "--", filename],
|
|
cwd=ROOT,
|
|
check=False,
|
|
)
|
|
self.assertEqual(1, result.returncode, f"ignored: {filename}")
|
|
|
|
def test_workflow_owns_exact_bundle_paths(self):
|
|
text = SYNC_WORKFLOW.read_text(encoding="utf-8")
|
|
match = re.search(
|
|
r"^ {10}managed_paths=\(\n"
|
|
r'(?P<body>(?:^ {12}"[^"]+"\n)+)'
|
|
r"^ {10}\)$",
|
|
text,
|
|
flags=re.MULTILINE,
|
|
)
|
|
self.assertIsNotNone(match)
|
|
managed_paths = tuple(
|
|
line.strip().strip('"') for line in match.group("body").splitlines()
|
|
)
|
|
|
|
self.assertEqual(EXPECTED_MANAGED_PATHS, managed_paths)
|
|
self.assertIn('rm -rf -- "${managed_paths[@]}"', text)
|
|
self.assertIn('cp -R -- "$bundle/$path" "$path"', text)
|
|
self.assertIn('git add -f -A -- "${managed_paths[@]}"', text)
|
|
self.assertNotIn('cp -R "$bundle"/. "$REPO_DIR"/', text)
|
|
|
|
|
|
def write_minimal_sources(repo_root: Path) -> None:
|
|
for relative in (
|
|
"docs/tsl",
|
|
"skills/tsl-syntax-reference",
|
|
"skills/tsl-api-reference",
|
|
"tools/tsl-codegen",
|
|
):
|
|
(repo_root / relative).mkdir(parents=True)
|
|
|
|
ruleset = repo_root / "rulesets" / "tsl" / "index.md"
|
|
ruleset.parent.mkdir(parents=True)
|
|
ruleset.write_text(
|
|
"# TSL 智能体规则\n",
|
|
encoding="utf-8",
|
|
newline="\n",
|
|
)
|
|
|
|
for filename, content in ROOT_CONFIG_CONTENTS.items():
|
|
(repo_root / filename).write_bytes(content)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|