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:
Quratulain-bilal
2026-06-24 00:09:17 +05:00
committed by GitHub
parent 0c975bbef7
commit 0ef53eb91f
2 changed files with 66 additions and 10 deletions

View File

@@ -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
}

View File

@@ -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() == ""