diff --git a/app/lib/linear_cli/cli.ex b/app/lib/linear_cli/cli.ex index d5be26b..3960e3f 100644 --- a/app/lib/linear_cli/cli.ex +++ b/app/lib/linear_cli/cli.ex @@ -516,6 +516,10 @@ defmodule LinearCli.CLI do long: "--no-mine", help: "List the most recent issues, not just your own" ], + no_profile: [ + long: "--no-profile", + help: "Ignore the active profile's team/project defaults" + ], full: [short: "-f", long: "--full", help: "Show full issue details"], all: [ long: "--all", diff --git a/app/lib/linear_cli/cli/commands.ex b/app/lib/linear_cli/cli/commands.ex index ce4ae82..7aa214b 100644 --- a/app/lib/linear_cli/cli/commands.ex +++ b/app/lib/linear_cli/cli/commands.ex @@ -233,9 +233,13 @@ defmodule LinearCli.CLI.Commands do passed explicitly always win over the active profile. """ def issue_list(%{flags: flags, options: options, unknown: ids}) do - team_key = options.team || Profiles.default_team() + no_profile = Map.get(flags, :no_profile, false) + team_key = options.team || unless no_profile, do: Profiles.default_team() - with {:ok, project_id} <- resolve_project_id(options.project || Profiles.default_project()) do + project_source = + options.project || unless no_profile, do: Profiles.default_project() + + with {:ok, project_id} <- resolve_project_id(project_source) do input = %{ ids: Enum.map(ids, &IssueHelpers.expand_issue_id/1), mine: !flags.no_mine, diff --git a/app/test/linear_cli/cli/issue_commands_test.exs b/app/test/linear_cli/cli/issue_commands_test.exs index a5510a0..dfa6892 100644 --- a/app/test/linear_cli/cli/issue_commands_test.exs +++ b/app/test/linear_cli/cli/issue_commands_test.exs @@ -282,6 +282,33 @@ defmodule LinearCli.CLI.IssueCommandsTest do assert Map.has_key?(filter, "canceledAt") end + test "--no-profile bypasses active profile defaults via the full CLI dispatch path" do + test_pid = self() + + Req.Test.stub(LinearCli.Api, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + decoded = Jason.decode!(body) + query = decoded["query"] + + if String.contains?(query, "projects(") do + raise "--no-profile must not query projects when --project wasn't given" + end + + send(test_pid, {:filter, decoded["variables"]["filter"]}) + Req.Test.json(conn, issues_response([issue_map()])) + end) + + output = + capture_io(fn -> + assert :ok = LinearCli.CLI.main(["issue", "list", "--no-profile"]) + end) + + assert output =~ "CRY-1" + assert_received {:filter, filter} + refute Map.has_key?(filter, "team") + refute Map.has_key?(filter, "project") + end + test "--status with an unknown type exits 1 (Optimus parse error)" do test_pid = self() halt = fn code -> send(test_pid, {:halted, code}) end diff --git a/app/test/linear_cli/cli/profile_defaults_test.exs b/app/test/linear_cli/cli/profile_defaults_test.exs index 5205f13..09f0a55 100644 --- a/app/test/linear_cli/cli/profile_defaults_test.exs +++ b/app/test/linear_cli/cli/profile_defaults_test.exs @@ -216,6 +216,114 @@ defmodule LinearCli.CLI.ProfileDefaultsTest do assert filter["project"] == %{"id" => %{"eq" => "p2"}} end + test "--no-profile bypasses the active profile's team/project defaults" do + {:ok, _} = Profiles.create("manhattan", team: "CRY", project: "Manhattan Rollout") + :ok = Profiles.activate("manhattan") + + test_pid = self() + + Req.Test.stub(LinearCli.Api, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + decoded = Jason.decode!(body) + query = decoded["query"] + + if String.contains?(query, "projects(first: $first") do + raise "--no-profile must not query projects when --project wasn't given" + end + + if String.contains?(query, "issues(filter") do + send(test_pid, {:filter, decoded["variables"]["filter"]}) + Req.Test.json(conn, issues_response([issue_map()])) + else + raise "no stub matched query: #{query}" + end + end) + + result = %{ + flags: %{no_mine: false, unassigned: false, full: false, no_profile: true}, + options: %{team: nil, project: nil, output: "text"}, + unknown: [] + } + + output = capture_io(fn -> assert :ok = Commands.issue_list(result) end) + + assert output =~ "CRY-1" + assert_received {:filter, filter} + refute Map.has_key?(filter, "team") + refute Map.has_key?(filter, "project") + end + + test "--no-profile with an explicit --team still applies the explicit team" do + {:ok, _} = Profiles.create("manhattan", team: "CRY", project: "Manhattan Rollout") + :ok = Profiles.activate("manhattan") + + test_pid = self() + + Req.Test.stub(LinearCli.Api, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + decoded = Jason.decode!(body) + query = decoded["query"] + + cond do + String.contains?(query, "issues(filter") -> + send(test_pid, {:filter, decoded["variables"]["filter"]}) + Req.Test.json(conn, issues_response([issue_map()])) + + true -> + raise "no stub matched query: #{query}" + end + end) + + result = %{ + flags: %{no_mine: false, unassigned: false, full: false, no_profile: true}, + options: %{team: "ENG", project: nil, output: "text"}, + unknown: [] + } + + capture_io(fn -> assert :ok = Commands.issue_list(result) end) + + assert_received {:filter, filter} + assert filter["team"] == %{"key" => %{"eq" => "ENG"}} + refute Map.has_key?(filter, "project") + end + + test "--no-profile with an explicit --project still applies the explicit project" do + {:ok, _} = Profiles.create("manhattan", team: "CRY", project: "Manhattan Rollout") + :ok = Profiles.activate("manhattan") + + test_pid = self() + + Req.Test.stub(LinearCli.Api, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + decoded = Jason.decode!(body) + query = decoded["query"] + + cond do + String.contains?(query, "projects(first: $first") -> + Req.Test.json(conn, all_projects([project_map("p3", "Platform Cleanup")])) + + String.contains?(query, "issues(filter") -> + send(test_pid, {:filter, decoded["variables"]["filter"]}) + Req.Test.json(conn, issues_response([issue_map()])) + + true -> + raise "no stub matched query: #{query}" + end + end) + + result = %{ + flags: %{no_mine: false, unassigned: false, full: false, no_profile: true}, + options: %{team: nil, project: "Platform Cleanup", output: "text"}, + unknown: [] + } + + capture_io(fn -> assert :ok = Commands.issue_list(result) end) + + assert_received {:filter, filter} + refute Map.has_key?(filter, "team") + assert filter["project"] == %{"id" => %{"eq" => "p3"}} + end + test "resolves bare issue numbers (positional ids) via the active profile's team" do {:ok, _} = Profiles.create("manhattan", team: "CRY") :ok = Profiles.activate("manhattan")