🐛 fix(tsl-playbook): sync node formatting configs

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.
This commit is contained in:
csh
2026-07-30 13:47:49 +08:00
parent 1ae798bcaa
commit 0dcd968188
5 changed files with 1323 additions and 2 deletions
+5 -1
View File
@@ -158,6 +158,10 @@ jobs:
# 本 workflow 在目标分支上仅拥有以下路径。 # 本 workflow 在目标分支上仅拥有以下路径。
managed_paths=( managed_paths=(
"AGENTS.md" "AGENTS.md"
"package.json"
"package-lock.json"
".prettierrc.json"
".prettierignore"
"docs/tsl" "docs/tsl"
"skills/tsl-syntax-reference" "skills/tsl-syntax-reference"
"skills/tsl-api-reference" "skills/tsl-api-reference"
@@ -185,7 +189,7 @@ jobs:
cp -R -- "$bundle/$path" "$path" cp -R -- "$bundle/$path" "$path"
done done
git add -A -- "${managed_paths[@]}" git add -f -A -- "${managed_paths[@]}"
if git diff --cached --quiet; then if git diff --cached --quiet; then
echo "No tsl-playbook changes to publish." echo "No tsl-playbook changes to publish."
-1
View File
@@ -35,4 +35,3 @@ __pycache__/
# Node.js # Node.js
node_modules/ node_modules/
package-lock.json
+1189
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -7,6 +7,12 @@ from pathlib import Path
DEFAULT_OUTPUT = Path("tmp") / "tsl-playbook" DEFAULT_OUTPUT = Path("tmp") / "tsl-playbook"
ROOT_CONFIG_FILES = (
"package.json",
"package-lock.json",
".prettierrc.json",
".prettierignore",
)
def copy_tree(src: Path, dst: Path) -> None: def copy_tree(src: Path, dst: Path) -> None:
@@ -26,7 +32,9 @@ def ensure_sources(repo_root: Path) -> tuple[Path, Path, Path, Path, Path]:
ruleset = repo_root / "rulesets" / "tsl" / "index.md" ruleset = repo_root / "rulesets" / "tsl" / "index.md"
codegen_toolkit = repo_root / "tools" / "tsl-codegen" codegen_toolkit = repo_root / "tools" / "tsl-codegen"
sources = (docs_tsl, syntax_skill, api_skill, ruleset, codegen_toolkit) sources = (docs_tsl, syntax_skill, api_skill, ruleset, codegen_toolkit)
root_configs = tuple(repo_root / filename for filename in ROOT_CONFIG_FILES)
missing = [str(path) for path in sources if not path.exists()] missing = [str(path) for path in sources if not path.exists()]
missing.extend(str(path) for path in root_configs if not path.is_file())
if missing: if missing:
raise FileNotFoundError("missing source path(s): " + ", ".join(missing)) raise FileNotFoundError("missing source path(s): " + ", ".join(missing))
return sources return sources
@@ -56,6 +64,8 @@ def build(output: Path, repo_root: Path) -> None:
(output / "AGENTS.md").write_text( (output / "AGENTS.md").write_text(
build_agents_text(ruleset), encoding="utf-8", newline="\n" build_agents_text(ruleset), encoding="utf-8", newline="\n"
) )
for filename in ROOT_CONFIG_FILES:
shutil.copy2(repo_root / filename, output / filename)
copy_tree(docs_tsl, output / "docs" / "tsl") copy_tree(docs_tsl, output / "docs" / "tsl")
copy_tree(syntax_skill, output / "skills" / "tsl-syntax-reference") copy_tree(syntax_skill, output / "skills" / "tsl-syntax-reference")
copy_tree(api_skill, output / "skills" / "tsl-api-reference") copy_tree(api_skill, output / "skills" / "tsl-api-reference")
+119
View File
@@ -0,0 +1,119 @@
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()