mirror of
https://github.com/github/spec-kit.git
synced 2026-08-03 06:26:30 +08:00
fix(workflows): escape rich markup in id-mismatch errors and validate --from source early
The two id-mismatch error paths interpolated repr() into Rich markup, so a stray bracket in a user typo could be parsed as markup. Route both through rich.markup.escape. `workflow add <source> --from <url>` also validated the source only after downloading. Validate it up front so a URL/path/typo fails without a network fetch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -606,6 +606,10 @@ def workflow_add(
|
||||
project_root = _require_specify_project()
|
||||
registry = WorkflowRegistry(project_root)
|
||||
workflows_dir = project_root / ".specify" / "workflows"
|
||||
# With --from, source names the expected workflow ID: validate it up
|
||||
# front so a URL/path/typo fails without a network fetch.
|
||||
if from_url is not None and not dev:
|
||||
_validate_workflow_id_or_exit(source)
|
||||
# Reject a symlinked .specify / .specify/workflows before any write so an
|
||||
# install can't escape the project root (covers the local, URL, and
|
||||
# catalog branches below — all write beneath workflows_dir).
|
||||
@@ -643,8 +647,8 @@ def workflow_add(
|
||||
|
||||
if expected_id is not None and definition.id != expected_id:
|
||||
console.print(
|
||||
f"[red]Error:[/red] Workflow ID in YAML ({definition.id!r}) "
|
||||
f"does not match the requested workflow ID ({expected_id!r})."
|
||||
f"[red]Error:[/red] Workflow ID in YAML ({_escape_markup(repr(definition.id))}) "
|
||||
f"does not match the requested workflow ID ({_escape_markup(repr(expected_id))})."
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
@@ -902,8 +906,8 @@ def _install_workflow_from_catalog(
|
||||
import shutil
|
||||
shutil.rmtree(workflow_dir, ignore_errors=True)
|
||||
console.print(
|
||||
f"[red]Error:[/red] Workflow ID in YAML ({definition.id!r}) "
|
||||
f"does not match catalog key ({workflow_id!r}). "
|
||||
f"[red]Error:[/red] Workflow ID in YAML ({_escape_markup(repr(definition.id))}) "
|
||||
f"does not match catalog key ({_escape_markup(repr(workflow_id))}). "
|
||||
f"The catalog entry may be misconfigured."
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
@@ -7332,6 +7332,30 @@ steps:
|
||||
assert "does not match" in result.output
|
||||
assert not WorkflowRegistry(project_dir).is_installed("align-wf")
|
||||
|
||||
def test_add_from_rejects_invalid_source_id_without_fetch(self, project_dir, monkeypatch):
|
||||
"""--from with a non-workflow-id source (URL, path, uppercase) fails before any network fetch."""
|
||||
from unittest.mock import patch
|
||||
from typer.testing import CliRunner
|
||||
from specify_cli import app
|
||||
|
||||
monkeypatch.chdir(project_dir)
|
||||
calls: list[str] = []
|
||||
|
||||
def _fake_open(url, timeout=None, extra_headers=None):
|
||||
calls.append(url)
|
||||
raise AssertionError(f"network fetch attempted: {url}")
|
||||
|
||||
runner = CliRunner()
|
||||
with patch("specify_cli.authentication.http.open_url", side_effect=_fake_open):
|
||||
for bad_source in ("https://x/y.yml", "./local.yml", "BadCase"):
|
||||
result = runner.invoke(
|
||||
app,
|
||||
["workflow", "add", bad_source, "--from", "https://example.com/workflow.yml"],
|
||||
)
|
||||
assert result.exit_code != 0
|
||||
assert "Invalid workflow ID" in result.output
|
||||
assert calls == []
|
||||
|
||||
# -- search --author -----------------------------------------------
|
||||
|
||||
def test_search_author_filters(self, project_dir, monkeypatch):
|
||||
|
||||
Reference in New Issue
Block a user