Skip to content

Commit d009846

Browse files
jawwad-aliclaude
andcommitted
fix(workflows): workflow add detects local YAML files case-insensitively
`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>
1 parent d7699c3 commit d009846

2 files changed

Lines changed: 59 additions & 2 deletions

File tree

src/specify_cli/workflows/_commands.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,7 +1555,7 @@ def _validate_and_install_local(
15551555
# precedence over --from so a URL that would be ignored is never fetched.
15561556
if dev:
15571557
dev_path = Path(source).expanduser()
1558-
if dev_path.is_file() and dev_path.suffix in (".yml", ".yaml"):
1558+
if dev_path.is_file() and dev_path.suffix.lower() in (".yml", ".yaml"):
15591559
_validate_and_install_local(dev_path, str(dev_path))
15601560
return
15611561
if dev_path.is_dir():
@@ -1714,7 +1714,7 @@ def _validate_and_install_local(
17141714
# Try as a local file/directory
17151715
source_path = Path(source)
17161716
if source_path.exists():
1717-
if source_path.is_file() and source_path.suffix in (".yml", ".yaml"):
1717+
if source_path.is_file() and source_path.suffix.lower() in (".yml", ".yaml"):
17181718
_validate_and_install_local(source_path, str(source_path))
17191719
return
17201720
elif source_path.is_dir():

tests/test_workflows.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7499,6 +7499,63 @@ def rename_boom(src, dst):
74997499
assert "[stage]permissiondenied" in output_compact
75007500
assert "[reg]diskfull" in output_compact
75017501

7502+
7503+
class TestWorkflowAddCaseInsensitiveSuffix:
7504+
"""`workflow add` must detect a local YAML file case-insensitively, matching
7505+
`workflow run` (_commands.py:workflow_run) and the engine loader
7506+
(engine.py:WorkflowEngine.load_workflow), which both use `.suffix.lower()`.
7507+
Without it, `workflow run Sample.YAML` works but `workflow add Sample.YAML`
7508+
fails — an add/run inconsistency for an uppercase extension."""
7509+
7510+
def test_plain_path_accepts_uppercase_extension(self, temp_dir, monkeypatch, sample_workflow_yaml):
7511+
from typer.testing import CliRunner
7512+
from specify_cli import app
7513+
7514+
(temp_dir / ".specify" / "workflows").mkdir(parents=True)
7515+
src = temp_dir / "Sample.YAML"
7516+
src.write_text(sample_workflow_yaml, encoding="utf-8")
7517+
7518+
monkeypatch.chdir(temp_dir)
7519+
result = CliRunner().invoke(app, ["workflow", "add", str(src)])
7520+
7521+
# Before the fix: `.suffix in (...)` is case-sensitive, so ".YAML" is not
7522+
# recognized as a local file; the path falls through to catalog lookup
7523+
# and fails. After the fix it installs like the lowercase happy path.
7524+
assert result.exit_code == 0, result.output
7525+
assert "installed" in result.output
7526+
7527+
def test_dev_path_accepts_uppercase_extension(self, temp_dir, monkeypatch, sample_workflow_yaml):
7528+
from typer.testing import CliRunner
7529+
from specify_cli import app
7530+
7531+
(temp_dir / ".specify" / "workflows").mkdir(parents=True)
7532+
src = temp_dir / "Sample.YAML"
7533+
src.write_text(sample_workflow_yaml, encoding="utf-8")
7534+
7535+
monkeypatch.chdir(temp_dir)
7536+
result = CliRunner().invoke(app, ["workflow", "add", "--dev", str(src)])
7537+
7538+
# Before the fix the --dev branch rejects ".YAML" with
7539+
# "--dev source must be a workflow YAML file ...".
7540+
assert result.exit_code == 0, result.output
7541+
assert "installed" in result.output
7542+
7543+
def test_lowercase_extension_still_installs(self, temp_dir, monkeypatch, sample_workflow_yaml):
7544+
"""Happy path (lowercase .yml) is unchanged by the case-normalization."""
7545+
from typer.testing import CliRunner
7546+
from specify_cli import app
7547+
7548+
(temp_dir / ".specify" / "workflows").mkdir(parents=True)
7549+
src = temp_dir / "sample.yml"
7550+
src.write_text(sample_workflow_yaml, encoding="utf-8")
7551+
7552+
monkeypatch.chdir(temp_dir)
7553+
result = CliRunner().invoke(app, ["workflow", "add", str(src)])
7554+
7555+
assert result.exit_code == 0, result.output
7556+
assert "installed" in result.output
7557+
7558+
75027559
class TestWorkflowAddSymlinkGuard:
75037560
def test_add_malformed_ipv6_url_exits_cleanly(self, temp_dir, monkeypatch):
75047561
"""A malformed IPv6 URL must produce a clean error, not a ValueError traceback."""

0 commit comments

Comments
 (0)