🐛 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:
@@ -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."
|
||||||
|
|||||||
@@ -35,4 +35,3 @@ __pycache__/
|
|||||||
|
|
||||||
# Node.js
|
# Node.js
|
||||||
node_modules/
|
node_modules/
|
||||||
package-lock.json
|
|
||||||
|
|||||||
Generated
+1189
File diff suppressed because it is too large
Load Diff
@@ -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")
|
||||||
|
|||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user