mirror of
https://github.com/github/spec-kit.git
synced 2026-08-15 14:02:04 +02:00
fix(scripts): stop check-prerequisites text mode crashing on a legacy code page
_check_file/_check_dir hard-code U+2713/U+2717 and print() them to sys.stdout. On Windows sys.stdout falls back to the ANSI code page whenever stdout is not a console — which is every time an agent or a workflow step captures the output — and U+2713 is unencodable in cp1252: stdout encoding: cp1252 UnicodeEncodeError: 'charmap' codec can't encode character '✓' So text mode aborted right after printing "AVAILABLE_DOCS:", losing every per-document line. Fall back to ASCII when stdout cannot encode the glyph. "[OK]"/"[FAIL]" is the rendering these markers already have in-tree: Test-FileExists in scripts/powershell/common.ps1 emits exactly those, and normalize_status_text in tests/parity_helpers.py maps the glyphs onto them, so the twins already treat the two forms as equivalent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
81bf741b92
commit
275663b463
@@ -130,14 +130,31 @@ def _print_paths_only(paths: FeaturePaths, json_mode: bool) -> None:
|
|||||||
print(f"TASKS: {paths.tasks}")
|
print(f"TASKS: {paths.tasks}")
|
||||||
|
|
||||||
|
|
||||||
|
def _status_marker(ok: bool) -> str:
|
||||||
|
"""Return the status glyph, downgraded to ASCII when stdout cannot encode it.
|
||||||
|
|
||||||
|
On Windows sys.stdout falls back to the ANSI code page whenever it is not a
|
||||||
|
console - a pipe or a file redirect, which is how agents and workflow steps
|
||||||
|
invoke these scripts - and U+2713 is unencodable in cp1252, so printing it
|
||||||
|
raised UnicodeEncodeError and aborted the report right after
|
||||||
|
"AVAILABLE_DOCS:". "[OK]"/"[FAIL]" is the ASCII rendering these markers
|
||||||
|
already have in-tree: see Test-FileExists in scripts/powershell/common.ps1
|
||||||
|
and normalize_status_text in tests/parity_helpers.py.
|
||||||
|
"""
|
||||||
|
glyph = "✓" if ok else "✗"
|
||||||
|
try:
|
||||||
|
glyph.encode(getattr(sys.stdout, "encoding", None) or "utf-8")
|
||||||
|
except (LookupError, UnicodeEncodeError):
|
||||||
|
return "[OK]" if ok else "[FAIL]"
|
||||||
|
return glyph
|
||||||
|
|
||||||
|
|
||||||
def _check_file(path: Path, description: str) -> None:
|
def _check_file(path: Path, description: str) -> None:
|
||||||
marker = "✓" if path.is_file() else "✗"
|
print(f" {_status_marker(path.is_file())} {description}")
|
||||||
print(f" {marker} {description}")
|
|
||||||
|
|
||||||
|
|
||||||
def _check_dir(path: Path, description: str) -> None:
|
def _check_dir(path: Path, description: str) -> None:
|
||||||
marker = "✓" if _dir_has_entries(path) else "✗"
|
print(f" {_status_marker(_dir_has_entries(path))} {description}")
|
||||||
print(f" {marker} {description}")
|
|
||||||
|
|
||||||
|
|
||||||
def _print_text_results(paths: FeaturePaths, include_tasks: bool) -> None:
|
def _print_text_results(paths: FeaturePaths, include_tasks: bool) -> None:
|
||||||
|
|||||||
@@ -181,6 +181,45 @@ def test_python_text_output_matches_bash(prereq_repo: Path) -> None:
|
|||||||
assert _normalize_status_text(py.stdout) == _normalize_status_text(bash.stdout)
|
assert _normalize_status_text(py.stdout) == _normalize_status_text(bash.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
def test_python_text_output_survives_a_legacy_stdout_code_page(
|
||||||
|
prereq_repo: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Text mode must not crash when stdout cannot encode the status glyphs.
|
||||||
|
|
||||||
|
On Windows sys.stdout falls back to the ANSI code page whenever it is not a
|
||||||
|
console — which is every time an agent or a workflow step captures the
|
||||||
|
output. U+2713 is unencodable in cp1252, so printing it raised
|
||||||
|
UnicodeEncodeError and truncated the report right after "AVAILABLE_DOCS:".
|
||||||
|
The ASCII fallback is the rendering these markers already have in-tree
|
||||||
|
(Test-FileExists in scripts/powershell/common.ps1, and
|
||||||
|
normalize_status_text here).
|
||||||
|
"""
|
||||||
|
feat = prereq_repo / "specs" / "001-my-feature"
|
||||||
|
feat.mkdir(parents=True)
|
||||||
|
(feat / "plan.md").write_text("# plan\n", encoding="utf-8")
|
||||||
|
(feat / "contracts").mkdir()
|
||||||
|
_write_feature_json(prereq_repo)
|
||||||
|
|
||||||
|
env = _clean_env()
|
||||||
|
env["PYTHONIOENCODING"] = "cp1252"
|
||||||
|
result = _run(_py_cmd(prereq_repo, "--include-tasks"), prereq_repo, env=env)
|
||||||
|
|
||||||
|
assert result.returncode == 0, result.stderr
|
||||||
|
assert "UnicodeEncodeError" not in result.stderr
|
||||||
|
assert "AVAILABLE_DOCS:" in result.stdout
|
||||||
|
# Every per-document line must still be there, not truncated away by the
|
||||||
|
# encode error, and rendered with the ASCII markers.
|
||||||
|
for doc in (
|
||||||
|
"research.md",
|
||||||
|
"data-model.md",
|
||||||
|
"contracts/",
|
||||||
|
"quickstart.md",
|
||||||
|
"tasks.md",
|
||||||
|
):
|
||||||
|
assert doc in result.stdout, (doc, result.stdout)
|
||||||
|
assert "[OK]" in result.stdout or "[FAIL]" in result.stdout, result.stdout
|
||||||
|
|
||||||
|
|
||||||
@requires_bash
|
@requires_bash
|
||||||
def test_python_help_output_matches_bash(prereq_repo: Path) -> None:
|
def test_python_help_output_matches_bash(prereq_repo: Path) -> None:
|
||||||
bash = _run(_bash_cmd(prereq_repo, "--help"), prereq_repo)
|
bash = _run(_bash_cmd(prereq_repo, "--help"), prereq_repo)
|
||||||
|
|||||||
Reference in New Issue
Block a user