Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/specify_cli/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,16 @@ def _resolve_event_command_argv(
# subprocess.run(shell=False); invoke via `pwsh -File` (PowerShell 7+),
# falling back to `powershell -File` (Windows PowerShell) when pwsh is
# absent (S6). The default Windows script type would otherwise fail.
launcher = shutil.which("pwsh") or shutil.which("powershell") or "pwsh"
# When NEITHER is on PATH, degrade to "no argv" like every other
# failure branch in this resolver (and its documented stdlib mirror,
# the generated dispatcher's `_resolve_argv`) — a bare "pwsh" here
# would make subprocess.run() raise FileNotFoundError, surfacing as a
# confusing "[Errno 2] No such file or directory: 'pwsh'" instead of
# the clean "No script found for event command" the caller reports
# for a genuinely missing script.
launcher = shutil.which("pwsh") or shutil.which("powershell")
if not launcher:
return None
return [launcher, "-File", str(script_abs), *rest_args]

# sh: the script is chmod'd executable during install on POSIX. On Windows
Expand Down
31 changes: 31 additions & 0 deletions tests/integrations/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1340,6 +1340,37 @@ def test_ps_variant_prefixed_with_powershell_launcher(self, tmp_path):
assert argv[1] == "-File"
assert PurePath(argv[2]).as_posix().endswith(".specify/scripts/powershell/boot.ps1")

def test_ps_variant_returns_none_when_no_launcher_available(self, tmp_path, monkeypatch):
"""When NEITHER pwsh nor powershell is on PATH, the resolver must
degrade to "no argv" like every other failure branch in this
function — not fall back to a bare "pwsh" string, which would make
subprocess.run() raise FileNotFoundError instead of the caller's
clean "No script found for event command" warning.

The generated dispatcher's documented stdlib mirror, `_resolve_argv`,
already does this correctly (`if not launcher: return None`).
"""
from specify_cli.events import _resolve_event_command_argv
import shutil as _shutil

cmd_dir = tmp_path / ".specify" / "templates" / "commands"
cmd_dir.mkdir(parents=True)
(cmd_dir / "boot.md").write_text(
"---\n"
"description: \"Boot\"\n"
"scripts:\n"
" ps: scripts/powershell/boot.ps1\n"
"---\nBody\n",
encoding="utf-8",
)
ps_dir = tmp_path / ".specify" / "scripts" / "powershell"
ps_dir.mkdir(parents=True)
(ps_dir / "boot.ps1").write_text("exit 0\n", encoding="utf-8")

monkeypatch.setattr(_shutil, "which", lambda name: None)
argv = _resolve_event_command_argv(cmd_dir / "boot.md", tmp_path, None)
assert argv is None

def test_run_command_executes_with_project_root_cwd(self, tmp_path):
"""R1: the event command runs with cwd set to the project root, not the
caller's arbitrary working directory, so project-relative script logic
Expand Down