From 80f4e7d649fa1aa920a95a5952333a24c1f93b59 Mon Sep 17 00:00:00 2001 From: jawwad-ali Date: Wed, 24 Jun 2026 02:40:32 +0500 Subject: [PATCH 1/2] fix(scripts): count subdirectory-only dirs as non-empty in PowerShell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Test-DirHasFiles (the documented PowerShell twin of bash check_dir) tested non-emptiness with `Get-ChildItem | Where-Object { -not $_.PSIsContainer }`, counting only top-level FILES and ignoring subdirectories. Bash check_dir (`-n $(ls -A ...)`) and the PowerShell JSON-path contracts checks (check-prerequisites.ps1 / setup-tasks.ps1, no PSIsContainer filter) both count ANY entry. So a contracts/ directory whose only contents are subdirectories (e.g. contracts/v1/openapi.yaml) was reported present by bash, by bash JSON, and by PowerShell JSON, but [FAIL]/absent by PowerShell text mode — the lone outlier. Drop the PSIsContainer filter so Test-DirHasFiles counts any entry, matching the other three code paths. Add bash + PowerShell parity tests asserting a subdir-only contracts/ dir is reported non-empty in both shells. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/powershell/common.ps1 | 6 ++++- tests/test_setup_tasks.py | 46 +++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/scripts/powershell/common.ps1 b/scripts/powershell/common.ps1 index f56fc26577..911485019c 100644 --- a/scripts/powershell/common.ps1 +++ b/scripts/powershell/common.ps1 @@ -209,7 +209,11 @@ function Test-FileExists { function Test-DirHasFiles { param([string]$Path, [string]$Description) - if ((Test-Path -Path $Path -PathType Container) -and (Get-ChildItem -Path $Path -ErrorAction SilentlyContinue | Where-Object { -not $_.PSIsContainer } | Select-Object -First 1)) { + # A directory counts as non-empty when it contains ANY entry (files or + # subdirectories), matching bash check_dir (`-n $(ls -A ...)`) and the JSON + # contracts checks. Filtering out subdirectories would mis-report a dir whose + # only contents are subdirectories (e.g. contracts/v1/openapi.yaml) as empty. + if ((Test-Path -Path $Path -PathType Container) -and (Get-ChildItem -Path $Path -ErrorAction SilentlyContinue | Select-Object -First 1)) { Write-Output " [OK] $Description" return $true } else { diff --git a/tests/test_setup_tasks.py b/tests/test_setup_tasks.py index 0e3fb85f41..1acaa29d7d 100644 --- a/tests/test_setup_tasks.py +++ b/tests/test_setup_tasks.py @@ -840,3 +840,49 @@ def test_setup_tasks_ps_errors_without_feature_context( output = result.stderr + result.stdout assert result.returncode != 0 assert "Feature directory not found" in output + + +# --------------------------------------------------------------------------- +# Directory non-emptiness parity: a dir whose only contents are subdirectories +# (e.g. contracts/v1/openapi.yaml) must count as non-empty in both shells. +# --------------------------------------------------------------------------- + +def _run_bash_check_dir(repo: Path, target: Path) -> subprocess.CompletedProcess: + script = repo / ".specify" / "scripts" / "bash" / "common.sh" + return subprocess.run( + ["bash", "-c", 'source "$1"; check_dir "$2" "contracts/"', "bash", str(script), str(target)], + cwd=repo, capture_output=True, text=True, check=False, env=_clean_env(), + ) + + +def _run_powershell_test_dir(repo: Path, target: Path) -> subprocess.CompletedProcess: + script = repo / ".specify" / "scripts" / "powershell" / "common.ps1" + exe = "pwsh" if HAS_PWSH else _WINDOWS_POWERSHELL + return subprocess.run( + [exe, "-NoProfile", "-Command", + '& { param($common, $dir) . $common; Test-DirHasFiles -Path $dir -Description "contracts/" }', + str(script), str(target)], + cwd=repo, capture_output=True, text=True, check=False, env=_clean_env(), + ) + + +@requires_bash +def test_check_dir_bash_counts_subdir_only_contracts(tasks_repo: Path) -> None: + """bash check_dir treats a dir containing only subdirectories as non-empty.""" + contracts = tasks_repo / "contracts" / "v1" + contracts.mkdir(parents=True) + (contracts / "openapi.yaml").write_text("openapi: 3.0\n", encoding="utf-8") + result = _run_bash_check_dir(tasks_repo, tasks_repo / "contracts") + assert result.returncode == 0, result.stderr + assert "✓" in result.stdout and "✗" not in result.stdout + + +@pytest.mark.skipif(not (HAS_PWSH or _WINDOWS_POWERSHELL), reason="no PowerShell available") +def test_test_dir_has_files_ps_counts_subdir_only_contracts(tasks_repo: Path) -> None: + """Test-DirHasFiles must match bash: a subdir-only dir counts as non-empty.""" + contracts = tasks_repo / "contracts" / "v1" + contracts.mkdir(parents=True) + (contracts / "openapi.yaml").write_text("openapi: 3.0\n", encoding="utf-8") + result = _run_powershell_test_dir(tasks_repo, tasks_repo / "contracts") + assert result.returncode == 0, result.stderr + assert "[OK]" in result.stdout and "[FAIL]" not in result.stdout From 6810368de6e5d3c308fede49f1d94abd97dff139 Mon Sep 17 00:00:00 2001 From: jawwad-ali Date: Wed, 24 Jun 2026 18:19:00 +0500 Subject: [PATCH 2/2] review: accurate non-empty comment + drop doubled test prefix Address review feedback on Test-DirHasFiles parity fix: - Reword the common.ps1 comment so it no longer claims exact `ls -A` parity (Get-ChildItem omits hidden entries without -Force); it now points at the in-repo PowerShell JSON contracts checks as the matching reference and keeps the subdir-only-is-non-empty rationale. - Rename test_test_dir_has_files_ps_... -> test_dir_has_files_ps_... to drop the doubled 'test_' prefix. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/powershell/common.ps1 | 10 ++++++---- tests/test_setup_tasks.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/powershell/common.ps1 b/scripts/powershell/common.ps1 index 911485019c..5161fce1ad 100644 --- a/scripts/powershell/common.ps1 +++ b/scripts/powershell/common.ps1 @@ -209,10 +209,12 @@ function Test-FileExists { function Test-DirHasFiles { param([string]$Path, [string]$Description) - # A directory counts as non-empty when it contains ANY entry (files or - # subdirectories), matching bash check_dir (`-n $(ls -A ...)`) and the JSON - # contracts checks. Filtering out subdirectories would mis-report a dir whose - # only contents are subdirectories (e.g. contracts/v1/openapi.yaml) as empty. + # A directory counts as non-empty when Get-ChildItem returns any entry + # (files or subdirectories) — matching the JSON contracts checks in + # check-prerequisites.ps1 / setup-tasks.ps1, and treating a directory whose + # only contents are subdirectories (e.g. contracts/v1/openapi.yaml) as + # non-empty like bash check_dir. Filtering out subdirectories would + # mis-report such a directory as empty. if ((Test-Path -Path $Path -PathType Container) -and (Get-ChildItem -Path $Path -ErrorAction SilentlyContinue | Select-Object -First 1)) { Write-Output " [OK] $Description" return $true diff --git a/tests/test_setup_tasks.py b/tests/test_setup_tasks.py index 1acaa29d7d..25faa1ed22 100644 --- a/tests/test_setup_tasks.py +++ b/tests/test_setup_tasks.py @@ -878,7 +878,7 @@ def test_check_dir_bash_counts_subdir_only_contracts(tasks_repo: Path) -> None: @pytest.mark.skipif(not (HAS_PWSH or _WINDOWS_POWERSHELL), reason="no PowerShell available") -def test_test_dir_has_files_ps_counts_subdir_only_contracts(tasks_repo: Path) -> None: +def test_dir_has_files_ps_counts_subdir_only_contracts(tasks_repo: Path) -> None: """Test-DirHasFiles must match bash: a subdir-only dir counts as non-empty.""" contracts = tasks_repo / "contracts" / "v1" contracts.mkdir(parents=True)