Skip to content
Merged
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
8 changes: 7 additions & 1 deletion src/specify_cli/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,13 @@ def _resolve_event_command_argv(
else:
base = project_root / ".specify"

tokens = shlex.split(script_cmd, posix=(os.name != "nt"))
try:
tokens = shlex.split(script_cmd, posix=(os.name != "nt"))
except ValueError:
# Mirror the generated dispatcher's _resolve_argv: a scripts: value
# shlex cannot tokenize (e.g. an unclosed quote) declares no runnable
# script, so degrade to "no argv" instead of raising.
return None
if not tokens:
return None
script_abs = base / tokens[0]
Expand Down
26 changes: 26 additions & 0 deletions tests/integrations/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,32 @@ def test_py_variant_anchored_under_specify(self, tmp_path):
assert PurePath(argv[1]).as_posix().endswith(".specify/scripts/python/boot.py")
assert ".specify" in argv[1]

def test_unparseable_script_command_returns_none(self, tmp_path):
"""A ``scripts:`` value shlex cannot tokenize must resolve to no argv.

The generated dispatcher's ``_resolve_argv`` twin wraps its
``shlex.split`` in ``except ValueError: return None``, but the
CLI-side resolver did not: an unclosed quote in a ``scripts:``
frontmatter value raised a raw ``ValueError: No closing quotation``
through ``resolve_and_run_event_command`` instead of degrading to
"no runnable script" like every other malformed-input case here.
"""
from specify_cli.events import _resolve_event_command_argv

cmd_dir = tmp_path / ".specify" / "templates" / "commands"
cmd_dir.mkdir(parents=True)
(cmd_dir / "boot.md").write_text(
"---\n"
"description: \"Boot\"\n"
"scripts:\n"
" sh: scripts/bash/boot.sh \"unclosed\n"
"---\nBody\n",
encoding="utf-8",
)

argv = _resolve_event_command_argv(cmd_dir / "boot.md", tmp_path, None)
assert argv is None

def test_ps_variant_prefixed_with_powershell_launcher(self, tmp_path):
"""S6: the ps variant prefixes argv with pwsh/powershell -File so
subprocess.run(shell=False) can execute the .ps1 script."""
Expand Down