diff --git a/src/specify_cli/bundler/services/primitives.py b/src/specify_cli/bundler/services/primitives.py index 31b1126a34..d4a0a6f8ce 100644 --- a/src/specify_cli/bundler/services/primitives.py +++ b/src/specify_cli/bundler/services/primitives.py @@ -337,7 +337,16 @@ def install(self, component: ComponentRef) -> None: with _chdir(self._root): _delegate_command( "install", f"workflow '{component.id}'", - lambda: workflow_add(component.id), + # Pass the options explicitly. Called in-process rather than + # through Typer, an omitted ``typer.Option`` parameter keeps its + # ``OptionInfo`` sentinel as the value -- which is TRUTHY and is + # not ``None`` -- so ``workflow_add``'s ``if dev:`` took the + # local-path branch for every catalog install and failed with + # "--dev source must be a workflow YAML file ...". It is the only + # one of the four delegated commands that declares options; + # workflow_remove / workflow_step_add / workflow_step_remove take + # a bare ``typer.Argument`` and are safe as written. + lambda: workflow_add(component.id, dev=False, from_url=None), ) def refresh(self, component: ComponentRef) -> None: diff --git a/tests/unit/test_bundler_primitives.py b/tests/unit/test_bundler_primitives.py index dc39106b50..8be15f39d1 100644 --- a/tests/unit/test_bundler_primitives.py +++ b/tests/unit/test_bundler_primitives.py @@ -78,7 +78,9 @@ def test_offline_workflow_allows_bundled(tmp_path: Path, monkeypatch): assets, "_locate_bundled_workflow", lambda wid: tmp_path / "wf" ) calls: list[str] = [] - monkeypatch.setattr(specify_cli, "workflow_add", lambda wid: calls.append(wid)) + monkeypatch.setattr( + specify_cli, "workflow_add", lambda wid, **kwargs: calls.append(wid) + ) manager = primitive_manager("workflows", tmp_path, allow_network=False) manager.install(_component("workflows", "bundled-wf")) @@ -86,6 +88,40 @@ def test_offline_workflow_allows_bundled(tmp_path: Path, monkeypatch): assert calls == ["bundled-wf"] +def test_workflow_install_passes_explicit_typer_options(tmp_path: Path, monkeypatch): + """The bundler calls ``workflow_add`` in-process, so it must pass the + ``typer.Option`` values explicitly. + + Outside Typer, an omitted option parameter keeps its ``OptionInfo`` + sentinel as the value. That sentinel is truthy and is not ``None``, so + ``workflow_add``'s ``if dev:`` took the local-path branch for *every* + catalog install and failed with "--dev source must be a workflow YAML + file, supported archive, or directory containing workflow.yml". + """ + import specify_cli + import specify_cli._assets as assets + + monkeypatch.setattr( + assets, "_locate_bundled_workflow", lambda wid: tmp_path / "wf" + ) + seen: list[dict] = [] + + def _capture(wid, *args, **kwargs): + seen.append({"id": wid, "args": args, "kwargs": kwargs}) + + monkeypatch.setattr(specify_cli, "workflow_add", _capture) + + manager = primitive_manager("workflows", tmp_path, allow_network=False) + manager.install(_component("workflows", "bundled-wf")) + + assert len(seen) == 1, seen + call = seen[0] + assert call["id"] == "bundled-wf" + # Both options must arrive as real values, never as Typer sentinels. + assert call["kwargs"].get("dev") is False, call["kwargs"] + assert call["kwargs"].get("from_url") is None, call["kwargs"] + + def test_assert_pinned_version_matches_passes(): from specify_cli.bundler.services.primitives import _assert_pinned_version