mirror of
https://github.com/github/spec-kit.git
synced 2026-07-03 12:28:06 +08:00
fix(scripts): send check-prerequisites.ps1 errors to stderr (#3123)
* fix(scripts): send check-prerequisites.ps1 errors to stderr The validation errors and run-hints in check-prerequisites.ps1 were written with Write-Output, so they went to stdout. This script is usually run with -Json and its stdout parsed by the agent, so an error (e.g. missing plan.md) leaves the parser with an error string instead of JSON. The bash counterpart already writes these to stderr (>&2), as do the sibling PowerShell scripts (setup-tasks.ps1, common.ps1's Get-FeaturePathsEnv). Switch the six error/hint lines to [Console]::Error.WriteLine so stdout stays clean and the two shells match. * test(scripts): assert check-prerequisites errors stay on stderr Per the #3122 bug assessment, tighten the failure-path tests so they verify stdout stays clean (empty / valid JSON) and the error text only appears on stderr, instead of checking the combined stdout+stderr string. Covers all three PowerShell validation paths (missing feature dir, missing plan.md, missing tasks.md with -RequireTasks) and the bash counterpart. The two new error-routing tests fail on the pre-fix script (errors on stdout) and pass after it.
This commit is contained in:
@@ -83,24 +83,24 @@ if ($PathsOnly) {
|
||||
|
||||
# Validate required directories and files
|
||||
if (-not (Test-Path $paths.FEATURE_DIR -PathType Container)) {
|
||||
Write-Output "ERROR: Feature directory not found: $($paths.FEATURE_DIR)"
|
||||
[Console]::Error.WriteLine("ERROR: Feature directory not found: $($paths.FEATURE_DIR)")
|
||||
$specifyCommand = Format-SpecKitCommand -CommandName 'specify' -RepoRoot $paths.REPO_ROOT
|
||||
Write-Output "Run $specifyCommand first to create the feature structure."
|
||||
[Console]::Error.WriteLine("Run $specifyCommand first to create the feature structure.")
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path $paths.IMPL_PLAN -PathType Leaf)) {
|
||||
Write-Output "ERROR: plan.md not found in $($paths.FEATURE_DIR)"
|
||||
[Console]::Error.WriteLine("ERROR: plan.md not found in $($paths.FEATURE_DIR)")
|
||||
$planCommand = Format-SpecKitCommand -CommandName 'plan' -RepoRoot $paths.REPO_ROOT
|
||||
Write-Output "Run $planCommand first to create the implementation plan."
|
||||
[Console]::Error.WriteLine("Run $planCommand first to create the implementation plan.")
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check for tasks.md if required
|
||||
if ($RequireTasks -and -not (Test-Path $paths.TASKS -PathType Leaf)) {
|
||||
Write-Output "ERROR: tasks.md not found in $($paths.FEATURE_DIR)"
|
||||
[Console]::Error.WriteLine("ERROR: tasks.md not found in $($paths.FEATURE_DIR)")
|
||||
$tasksCommand = Format-SpecKitCommand -CommandName 'tasks' -RepoRoot $paths.REPO_ROOT
|
||||
Write-Output "Run $tasksCommand first to create the task list."
|
||||
[Console]::Error.WriteLine("Run $tasksCommand first to create the task list.")
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,11 @@ def test_paths_only_text_mode_on_non_spec_branch(prereq_repo: Path) -> None:
|
||||
|
||||
@requires_bash
|
||||
def test_normal_mode_still_validates_branch(prereq_repo: Path) -> None:
|
||||
"""Without --paths-only, feature directory validation must still fail on main."""
|
||||
"""Without --paths-only, feature directory validation must still fail on main.
|
||||
|
||||
The error must go to stderr and stdout must stay clean, so a caller that
|
||||
parses stdout as JSON is not handed the error string instead (#3122).
|
||||
"""
|
||||
script = prereq_repo / ".specify" / "scripts" / "bash" / "check-prerequisites.sh"
|
||||
result = subprocess.run(
|
||||
["bash", str(script), "--json"],
|
||||
@@ -155,6 +159,8 @@ def test_normal_mode_still_validates_branch(prereq_repo: Path) -> None:
|
||||
)
|
||||
assert result.returncode != 0
|
||||
assert "Feature directory not found" in result.stderr
|
||||
assert "Feature directory not found" not in result.stdout
|
||||
assert result.stdout.strip() == ""
|
||||
|
||||
|
||||
# ── PowerShell tests ──────────────────────────────────────────────────────
|
||||
@@ -213,7 +219,11 @@ def test_ps_paths_only_succeeds_on_spec_branch(prereq_repo: Path) -> None:
|
||||
|
||||
@pytest.mark.skipif(not (HAS_PWSH or _WINDOWS_POWERSHELL), reason="no PowerShell available")
|
||||
def test_ps_normal_mode_still_validates_branch(prereq_repo: Path) -> None:
|
||||
"""Without -PathsOnly, feature directory validation must still fail on main."""
|
||||
"""Without -PathsOnly, feature directory validation must still fail on main.
|
||||
|
||||
The error must land on stderr only, leaving stdout clean for -Json
|
||||
callers that parse it as JSON (#3122).
|
||||
"""
|
||||
script = prereq_repo / ".specify" / "scripts" / "powershell" / "check-prerequisites.ps1"
|
||||
exe = "pwsh" if HAS_PWSH else _WINDOWS_POWERSHELL
|
||||
result = subprocess.run(
|
||||
@@ -225,5 +235,51 @@ def test_ps_normal_mode_still_validates_branch(prereq_repo: Path) -> None:
|
||||
env=_clean_env(),
|
||||
)
|
||||
assert result.returncode != 0
|
||||
combined = result.stdout + result.stderr
|
||||
assert "Feature directory not found" in combined
|
||||
assert "Feature directory not found" in result.stderr
|
||||
assert "Feature directory not found" not in result.stdout
|
||||
assert result.stdout.strip() == ""
|
||||
|
||||
|
||||
@pytest.mark.skipif(not (HAS_PWSH or _WINDOWS_POWERSHELL), reason="no PowerShell available")
|
||||
def test_ps_missing_plan_error_goes_to_stderr(prereq_repo: Path) -> None:
|
||||
"""A missing plan.md must report on stderr, not stdout (#3122)."""
|
||||
feat = prereq_repo / "specs" / "001-my-feature"
|
||||
feat.mkdir(parents=True, exist_ok=True)
|
||||
_write_feature_json(prereq_repo)
|
||||
script = prereq_repo / ".specify" / "scripts" / "powershell" / "check-prerequisites.ps1"
|
||||
exe = "pwsh" if HAS_PWSH else _WINDOWS_POWERSHELL
|
||||
result = subprocess.run(
|
||||
[exe, "-NoProfile", "-File", str(script), "-Json"],
|
||||
cwd=prereq_repo,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
env=_clean_env(),
|
||||
)
|
||||
assert result.returncode != 0
|
||||
assert "plan.md not found" in result.stderr
|
||||
assert "plan.md not found" not in result.stdout
|
||||
assert result.stdout.strip() == ""
|
||||
|
||||
|
||||
@pytest.mark.skipif(not (HAS_PWSH or _WINDOWS_POWERSHELL), reason="no PowerShell available")
|
||||
def test_ps_missing_tasks_error_goes_to_stderr(prereq_repo: Path) -> None:
|
||||
"""With -RequireTasks, a missing tasks.md must report on stderr only (#3122)."""
|
||||
feat = prereq_repo / "specs" / "001-my-feature"
|
||||
feat.mkdir(parents=True, exist_ok=True)
|
||||
(feat / "plan.md").write_text("# plan\n", encoding="utf-8")
|
||||
_write_feature_json(prereq_repo)
|
||||
script = prereq_repo / ".specify" / "scripts" / "powershell" / "check-prerequisites.ps1"
|
||||
exe = "pwsh" if HAS_PWSH else _WINDOWS_POWERSHELL
|
||||
result = subprocess.run(
|
||||
[exe, "-NoProfile", "-File", str(script), "-Json", "-RequireTasks"],
|
||||
cwd=prereq_repo,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
env=_clean_env(),
|
||||
)
|
||||
assert result.returncode != 0
|
||||
assert "tasks.md not found" in result.stderr
|
||||
assert "tasks.md not found" not in result.stdout
|
||||
assert result.stdout.strip() == ""
|
||||
|
||||
Reference in New Issue
Block a user