mirror of
https://github.com/github/spec-kit.git
synced 2026-08-03 06:26:30 +08:00
fix(workflows): workflow add detects local YAML files case-insensitively (#3633)
`workflow add` gated the local-file branches on a case-SENSITIVE
`.suffix in (".yml", ".yaml")` (the `--dev` branch and the plain
local-path branch), while every other YAML-file detector in the CLI
normalizes case: `workflow run` uses `source_path.suffix.lower()` and
`WorkflowEngine.load_workflow` uses `path.suffix.lower()`.
The result was an add/run inconsistency: `specify workflow run Sample.YAML`
loads the file, but `specify workflow add Sample.YAML` does not recognize
it as a local workflow — the `--dev` branch rejects it with "--dev source
must be a workflow YAML file ..." and the plain path falls through to a
catalog lookup that fails with "not found in catalog".
Add `.lower()` to both suffix reads so `workflow add` matches its siblings.
The lowercase happy path is unchanged.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1555,7 +1555,7 @@ def workflow_add(
|
||||
# precedence over --from so a URL that would be ignored is never fetched.
|
||||
if dev:
|
||||
dev_path = Path(source).expanduser()
|
||||
if dev_path.is_file() and dev_path.suffix in (".yml", ".yaml"):
|
||||
if dev_path.is_file() and dev_path.suffix.lower() in (".yml", ".yaml"):
|
||||
_validate_and_install_local(dev_path, str(dev_path))
|
||||
return
|
||||
if dev_path.is_dir():
|
||||
@@ -1714,7 +1714,7 @@ def workflow_add(
|
||||
# Try as a local file/directory
|
||||
source_path = Path(source)
|
||||
if source_path.exists():
|
||||
if source_path.is_file() and source_path.suffix in (".yml", ".yaml"):
|
||||
if source_path.is_file() and source_path.suffix.lower() in (".yml", ".yaml"):
|
||||
_validate_and_install_local(source_path, str(source_path))
|
||||
return
|
||||
elif source_path.is_dir():
|
||||
|
||||
@@ -7786,6 +7786,63 @@ class TestWorkflowRemoveGuard:
|
||||
assert "[stage]permissiondenied" in output_compact
|
||||
assert "[reg]diskfull" in output_compact
|
||||
|
||||
|
||||
class TestWorkflowAddCaseInsensitiveSuffix:
|
||||
"""`workflow add` must detect a local YAML file case-insensitively, matching
|
||||
`workflow run` (_commands.py:workflow_run) and the engine loader
|
||||
(engine.py:WorkflowEngine.load_workflow), which both use `.suffix.lower()`.
|
||||
Without it, `workflow run Sample.YAML` works but `workflow add Sample.YAML`
|
||||
fails — an add/run inconsistency for an uppercase extension."""
|
||||
|
||||
def test_plain_path_accepts_uppercase_extension(self, temp_dir, monkeypatch, sample_workflow_yaml):
|
||||
from typer.testing import CliRunner
|
||||
from specify_cli import app
|
||||
|
||||
(temp_dir / ".specify" / "workflows").mkdir(parents=True)
|
||||
src = temp_dir / "Sample.YAML"
|
||||
src.write_text(sample_workflow_yaml, encoding="utf-8")
|
||||
|
||||
monkeypatch.chdir(temp_dir)
|
||||
result = CliRunner().invoke(app, ["workflow", "add", str(src)])
|
||||
|
||||
# Before the fix: `.suffix in (...)` is case-sensitive, so ".YAML" is not
|
||||
# recognized as a local file; the path falls through to catalog lookup
|
||||
# and fails. After the fix it installs like the lowercase happy path.
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "installed" in result.output
|
||||
|
||||
def test_dev_path_accepts_uppercase_extension(self, temp_dir, monkeypatch, sample_workflow_yaml):
|
||||
from typer.testing import CliRunner
|
||||
from specify_cli import app
|
||||
|
||||
(temp_dir / ".specify" / "workflows").mkdir(parents=True)
|
||||
src = temp_dir / "Sample.YAML"
|
||||
src.write_text(sample_workflow_yaml, encoding="utf-8")
|
||||
|
||||
monkeypatch.chdir(temp_dir)
|
||||
result = CliRunner().invoke(app, ["workflow", "add", "--dev", str(src)])
|
||||
|
||||
# Before the fix the --dev branch rejects ".YAML" with
|
||||
# "--dev source must be a workflow YAML file ...".
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "installed" in result.output
|
||||
|
||||
def test_lowercase_extension_still_installs(self, temp_dir, monkeypatch, sample_workflow_yaml):
|
||||
"""Happy path (lowercase .yml) is unchanged by the case-normalization."""
|
||||
from typer.testing import CliRunner
|
||||
from specify_cli import app
|
||||
|
||||
(temp_dir / ".specify" / "workflows").mkdir(parents=True)
|
||||
src = temp_dir / "sample.yml"
|
||||
src.write_text(sample_workflow_yaml, encoding="utf-8")
|
||||
|
||||
monkeypatch.chdir(temp_dir)
|
||||
result = CliRunner().invoke(app, ["workflow", "add", str(src)])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "installed" in result.output
|
||||
|
||||
|
||||
class TestWorkflowAddSymlinkGuard:
|
||||
def test_add_malformed_ipv6_url_exits_cleanly(self, temp_dir, monkeypatch):
|
||||
"""A malformed IPv6 URL must produce a clean error, not a ValueError traceback."""
|
||||
|
||||
Reference in New Issue
Block a user