From 9e852cf681de3037e35e0bef426010b6fda96b35 Mon Sep 17 00:00:00 2001 From: bougyman's bot Date: Thu, 24 Sep 2026 17:35:52 -0400 Subject: [PATCH 1/2] feat(issue): add batch unassign command --- Readme.adoc | 18 +- app/lib/linear_cli/cli.ex | 12 +- .../cli/commands/issues/mutations.ex | 51 ++++- app/lib/linear_cli/linear.ex | 1 + app/lib/linear_cli/linear/issue.ex | 19 +- .../cli/commands/issues/mutations_test.exs | 194 ++++++++++++++++++ .../linear_cli/cli/profile_defaults_test.exs | 48 +++++ app/test/linear_cli/linear/issue_test.exs | 44 ++++ documents/ash-domain-erd.adoc | 25 ++- 9 files changed, 401 insertions(+), 11 deletions(-) diff --git a/Readme.adoc b/Readme.adoc index 1b1705c..7815be2 100644 --- a/Readme.adoc +++ b/Readme.adoc @@ -25,7 +25,7 @@ endif::[] // x-release-please-end A command line interface to https://linear.app - list, take, create, develop, -and update issues without leaving your terminal. +unassign, and update issues without leaving your terminal. [cols="1,1",frame=none,grid=none] |=== @@ -175,6 +175,7 @@ so you don't have to type the full name every time: |`issue list` |`l`, `ls` |`issue status` |`s`, `st`, `stat` |`issue assign` |`a` +|`issue unassign` | |`issue move` |`m`, `mv` |`issue update` |`u` |`issue view` |`v` @@ -285,6 +286,21 @@ $ lc issue assign CRY-1234 <4> <3> Short form of `--status` <4> Prompts for the assignee interactively +==== Unassign one or more issues + +This command clears the assignee from each listed issue. It accepts explicit +issue identifiers only. + +[source,sh] +---- +$ lc issue unassign CRY-1234 +$ lc issue unassign CRY-1 CRY-2 +$ lc issue unassign --output json CRY-1 CRY-2 +---- + +Text output shows each updated issue and a confirmation. JSON output returns +one object for one issue and an array for multiple issues. + ==== Create an issue [source,sh] diff --git a/app/lib/linear_cli/cli.ex b/app/lib/linear_cli/cli.ex index c0267e8..312e137 100644 --- a/app/lib/linear_cli/cli.ex +++ b/app/lib/linear_cli/cli.ex @@ -264,6 +264,9 @@ defmodule LinearCli.CLI do defp dispatch([:issue, :status], result, context), do: run(&Mutations.issue_status/1, result, context) + defp dispatch([:issue, :unassign], result, context), + do: run(&Mutations.issue_unassign/1, result, context) + defp dispatch([:issue, :update], result, context), do: run(&Mutations.issue_update/1, result, context) @@ -319,7 +322,7 @@ defmodule LinearCli.CLI do end end - # `issue list`/`take`/`status`/`update` all set `allow_unknown_args: true` so bare + # `issue list`/`take`/`status`/`unassign`/`update` all set `allow_unknown_args: true` so bare # tokens (e.g. `CRY-1`) can be captured as issue ids via `result.unknown` # rather than a declared positional arg (Optimus has no `type: :array` # equivalent - see their subcommand specs below). That same bucket also @@ -329,7 +332,7 @@ defmodule LinearCli.CLI do # clearly. Every other subcommand has `allow_unknown_args: false` (the # default), where Optimus itself already rejects unknown args before we # ever see a parse_result - so `result.unknown` is only ever non-empty here - # for those four subcommands, and only ever contains genuine bare ids + # for these subcommands, and only ever contains genuine bare ids # once this filters out anything flag-shaped. defp reject_unknown_flags(unknown_tokens) do case Enum.filter(unknown_tokens, &String.starts_with?(&1, "-")) do @@ -904,6 +907,11 @@ defmodule LinearCli.CLI do ] ] ], + unassign: [ + name: "unassign", + about: "Clear the assignee from one or more issues (ISSUE_ID...)", + allow_unknown_args: true + ], take: [ name: "take", about: "Assign one or more issues to yourself", diff --git a/app/lib/linear_cli/cli/commands/issues/mutations.ex b/app/lib/linear_cli/cli/commands/issues/mutations.ex index d95ec63..4dc0c7d 100644 --- a/app/lib/linear_cli/cli/commands/issues/mutations.ex +++ b/app/lib/linear_cli/cli/commands/issues/mutations.ex @@ -1,6 +1,6 @@ defmodule LinearCli.CLI.Commands.Issues.Mutations do @moduledoc """ - Issue mutation commands: update, comment, status, and assign. + Issue mutation commands: update, comment, status, assign, and unassign. Ported from vendor/ruby-linear-cli/lib/linear/commands/issue/update.rb, comment.rb, status.rb, and assign.rb. """ @@ -119,6 +119,26 @@ defmodule LinearCli.CLI.Commands.Issues.Mutations do end end + @doc """ + Clears the assignee from one or more issues. + + Optimus captures the explicit issue IDs in `unknown`, since it has no + variadic positional-argument type. Each issue is resolved before its + assignee is cleared. Updates run concurrently with the same limit and input + order as the other batch issue mutations. + """ + @spec issue_unassign(Optimus.ParseResult.t()) :: :ok | {:error, term()} + def issue_unassign(%{unknown: issue_ids, options: options}) do + with :ok <- validate_issue_ids(issue_ids), + {:ok, issues} <- + Linear.issues(%{ids: Enum.map(issue_ids, &Identifiers.expand_issue_id/1)}), + {:ok, updated_issues} <- unassign_issues(issues) do + Display.show(one_or_many(updated_issues), %{output: options.output}) + print_unassign_results(updated_issues, options.output) + :ok + end + end + @doc """ Assigns an issue to a team member. @@ -203,6 +223,35 @@ defmodule LinearCli.CLI.Commands.Issues.Mutations do end) end + defp unassign_issues([]), do: {:ok, []} + + defp unassign_issues(issues) do + issues + |> Task.async_stream( + &Linear.unassign_issue/1, + max_concurrency: min(length(issues), @max_concurrent_issue_updates), + ordered: true, + timeout: 30_000 + ) + |> Enum.reduce_while({:ok, []}, fn + {:ok, {:ok, updated}}, {:ok, acc} -> {:cont, {:ok, [updated | acc]}} + {:ok, {:error, reason}}, _acc -> {:halt, {:error, reason}} + {:exit, reason}, _acc -> {:halt, {:error, {:task_exit, reason}}} + end) + |> then(fn + {:ok, updated_issues} -> {:ok, Enum.reverse(updated_issues)} + error -> error + end) + end + + defp print_unassign_results(_updated_issues, "json"), do: :ok + + defp print_unassign_results(updated_issues, _output) do + Enum.each(updated_issues, fn updated -> + Prompt.ok("#{updated.identifier} unassigned") + end) + end + defp one_or_many([one]), do: one defp one_or_many(many), do: many diff --git a/app/lib/linear_cli/linear.ex b/app/lib/linear_cli/linear.ex index 5a0d74f..97d37ec 100644 --- a/app/lib/linear_cli/linear.ex +++ b/app/lib/linear_cli/linear.ex @@ -34,6 +34,7 @@ defmodule LinearCli.Linear do define :issues, action: :list define :create_issue, action: :create, args: [:title, :description, :team_id] define :assign_issue, action: :assign, args: [:assignee_id] + define :unassign_issue, action: :unassign define :attach_issue_to_project, action: :attach_to_project, args: [:project_id] define :close_issue, action: :close, args: [:state_id] define :set_issue_status, action: :set_status, args: [:state_id] diff --git a/app/lib/linear_cli/linear/issue.ex b/app/lib/linear_cli/linear/issue.ex index ca6008c..6e4460f 100644 --- a/app/lib/linear_cli/linear/issue.ex +++ b/app/lib/linear_cli/linear/issue.ex @@ -39,6 +39,10 @@ defmodule LinearCli.Linear.Issue do manual LinearCli.Linear.Issue.Update.Assign end + update :unassign do + manual LinearCli.Linear.Issue.Update.Unassign + end + # Ruby: Issue#attach_to_project(project) update :attach_to_project do argument :project_id, :string, allow_nil?: false @@ -412,8 +416,8 @@ defmodule LinearCli.Linear.Issue.Update do alias LinearCli.Linear.Issue # Ruby: Issue#update!(input) - the shared issueUpdate mutation that - # assign!/attach_to_project!/close! all delegate to, refetching the - # updated issue via Issue.full_fragment. + # the issue update actions delegate to, refetching the updated issue via + # Issue.full_fragment. def run(identifier, input) do case Api.call(document(), %{"id" => identifier, "input" => input}) do {:ok, %{"issueUpdate" => %{"issue" => issue_map}}} when is_map(issue_map) -> @@ -451,6 +455,17 @@ defmodule LinearCli.Linear.Issue.Update.Assign do end end +defmodule LinearCli.Linear.Issue.Update.Unassign do + @moduledoc false + use Ash.Resource.ManualUpdate + + alias LinearCli.Linear.Issue + + def update(changeset, _opts, _context) do + Issue.Update.run(changeset.data.identifier, %{"assigneeId" => nil}) + end +end + defmodule LinearCli.Linear.Issue.Update.AttachToProject do @moduledoc false use Ash.Resource.ManualUpdate diff --git a/app/test/linear_cli/cli/commands/issues/mutations_test.exs b/app/test/linear_cli/cli/commands/issues/mutations_test.exs index 3717420..1c292fa 100644 --- a/app/test/linear_cli/cli/commands/issues/mutations_test.exs +++ b/app/test/linear_cli/cli/commands/issues/mutations_test.exs @@ -10,6 +10,200 @@ defmodule LinearCli.CLI.Commands.Issues.MutationsTest do %{"id" => id, "name" => name, "position" => position, "type" => type, "description" => nil} end + describe "issue unassign edge cases" do + test "sends a null assignee and confirms each issue in text output" 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"] + + cond do + String.contains?(query, "issue(id: $id)") -> + Req.Test.json(conn, %{"data" => %{"issue" => issue_map()}}) + + String.contains?(query, "issueUpdate") -> + send(test_pid, {:unassign_input, decoded["variables"]["input"]}) + Req.Test.json(conn, issue_updated(%{"assignee" => nil})) + + true -> + raise "no stub matched query: #{query}" + end + end) + + output = + capture_io(fn -> + assert :ok = LinearCli.CLI.main(["issue", "unassign", "CRY-1"]) + end) + + assert_received {:unassign_input, %{"assigneeId" => nil}} + assert output =~ "CRY-1 unassigned" + end + + test "returns a single issue object for JSON output" do + stub_responses([ + {"issue(id: $id)", %{"data" => %{"issue" => issue_map()}}}, + {"issueUpdate", issue_updated(%{"assignee" => nil})} + ]) + + output = + capture_io(fn -> + assert :ok = LinearCli.CLI.main(["issue", "unassign", "--output", "json", "CRY-1"]) + end) + + assert {:ok, decoded} = Jason.decode(output) + assert decoded["identifier"] == "CRY-1" + assert is_nil(decoded["assignee"]) + end + + test "updates multiple issue IDs concurrently and preserves JSON order" do + test_pid = self() + + issue_details = fn + "CRY-1" -> {"i1", "CRY-1"} + "CRY-2" -> {"i2", "CRY-2"} + end + + Req.Test.stub(LinearCli.Api, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + decoded = Jason.decode!(body) + query = decoded["query"] + variables = decoded["variables"] || %{} + + cond do + String.contains?(query, "issue(id: $id)") -> + {_id, identifier} = issue_details.(variables["id"]) + + Req.Test.json(conn, %{ + "data" => %{"issue" => issue_map(%{"identifier" => identifier})} + }) + + String.contains?(query, "issueUpdate") -> + {_id, identifier} = issue_details.(variables["id"]) + assert variables["input"] == %{"assigneeId" => nil} + update_pid = self() + send(test_pid, {:unassign_started, identifier, update_pid}) + + receive do + :finish_unassign -> :ok + after + 2_000 -> raise "unassign update was not released by the concurrency assertion" + end + + Req.Test.json( + conn, + issue_updated(%{"identifier" => identifier, "assignee" => nil}) + ) + + true -> + raise "no stub matched query: #{query}" + end + end) + + command = + Task.async(fn -> + capture_io(fn -> + assert :ok = + LinearCli.CLI.main([ + "issue", + "unassign", + "--output", + "json", + "CRY-1", + "CRY-2" + ]) + end) + end) + + assert_receive {:unassign_started, "CRY-1", first_update}, 1_000 + assert_receive {:unassign_started, "CRY-2", second_update}, 1_000 + send(first_update, :finish_unassign) + send(second_update, :finish_unassign) + + output = Task.await(command) + assert {:ok, decoded} = Jason.decode(output) + assert Enum.map(decoded, & &1["identifier"]) == ["CRY-1", "CRY-2"] + end + + test "with no issue IDs, exits 22" do + test_pid = self() + halt = fn code -> send(test_pid, {:halted, code}) end + + output = + capture_stderr(fn stderr -> + LinearCli.CLI.main(["issue", "unassign"], halt, stderr: stderr) + end) + + assert_received {:halted, 22} + assert output =~ "No issue IDs provided!" + end + + test "rejects unrecognized options before making a GraphQL call" do + test_pid = self() + halt = fn code -> send(test_pid, {:halted, code}) end + + Req.Test.stub(LinearCli.Api, fn _conn -> + raise "unassign must reject the option before making a GraphQL call" + end) + + output = + capture_stderr(fn stderr -> + LinearCli.CLI.main( + ["issue", "unassign", "--statuz", "CRY-1"], + halt, + stderr: stderr + ) + end) + + assert_received {:halted, 22} + assert output =~ "unrecognized option(s): --statuz" + end + + test "an unknown issue ID exits 66" do + test_pid = self() + halt = fn code -> send(test_pid, {:halted, code}) end + + Req.Test.stub(LinearCli.Api, fn conn -> + Req.Test.json(conn, %{"data" => %{"issue" => nil}}) + end) + + output = + capture_stderr(fn stderr -> + LinearCli.CLI.main(["issue", "unassign", "CRY-999"], halt, stderr: stderr) + end) + + assert_received {:halted, 66} + assert output =~ "No issue found with id CRY-999" + end + + test "preserves the generic mutation-error catch-all and exit 88" do + test_pid = self() + halt = fn code -> send(test_pid, {:halted, code}) end + + Req.Test.stub(LinearCli.Api, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + query = Jason.decode!(body)["query"] + + if String.contains?(query, "issue(id: $id)") do + Req.Test.json(conn, %{"data" => %{"issue" => issue_map()}}) + else + Req.Test.json(conn, %{"errors" => [%{"message" => "mutation denied"}]}) + end + end) + + output = + capture_stderr(fn stderr -> + LinearCli.CLI.main(["issue", "unassign", "CRY-1"], halt, stderr: stderr) + end) + + assert_received {:halted, 88} + assert output =~ "What the heck is this? ** (Ash.Error.Invalid)" + assert output =~ "** WTH? Cannot Continue **" + refute output =~ "mutation denied" + end + end + describe "issue status" do defp issue_with_state(state_id, state_name) do issue_map(%{"state" => %{"id" => state_id, "name" => state_name, "type" => "started"}}) diff --git a/app/test/linear_cli/cli/profile_defaults_test.exs b/app/test/linear_cli/cli/profile_defaults_test.exs index c90ea38..089a34a 100644 --- a/app/test/linear_cli/cli/profile_defaults_test.exs +++ b/app/test/linear_cli/cli/profile_defaults_test.exs @@ -398,6 +398,54 @@ defmodule LinearCli.CLI.ProfileDefaultsTest do end end + describe "Mutations.issue_unassign/1 resolves bare issue numbers via the active profile" do + test "expands a bare positional id before looking it up" do + {:ok, _} = Profiles.create("manhattan", team: "CRY") + :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, "issue(id: $id)") -> + send(test_pid, {:id, decoded["variables"]["id"]}) + + Req.Test.json( + conn, + %{"data" => %{"issue" => issue_map(%{"identifier" => "CRY-42"})}} + ) + + String.contains?(query, "issueUpdate") -> + assert decoded["variables"]["input"] == %{"assigneeId" => nil} + + Req.Test.json( + conn, + %{ + "data" => %{ + "issueUpdate" => %{ + "issue" => issue_map(%{"identifier" => "CRY-42", "assignee" => nil}) + } + } + } + ) + + true -> + raise "no stub matched query: #{query}" + end + end) + + result = %{unknown: ["42"], options: %{output: "text"}} + output = capture_io(fn -> assert :ok = Mutations.issue_unassign(result) end) + + assert_received {:id, "CRY-42"} + assert output =~ "CRY-42 unassigned" + end + end + describe "Development.issue_develop/2, issue_pr/2, issue_take/2 resolve bare issue numbers via the active profile" do test "issue_develop/2 expands the bare issue_id before self-assigning/checking it out" do {:ok, _} = Profiles.create("manhattan", team: "CRY") diff --git a/app/test/linear_cli/linear/issue_test.exs b/app/test/linear_cli/linear/issue_test.exs index 239011d..05e2803 100644 --- a/app/test/linear_cli/linear/issue_test.exs +++ b/app/test/linear_cli/linear/issue_test.exs @@ -379,6 +379,50 @@ defmodule LinearCli.Linear.IssueTest do end end + describe "unassign_issue/1" do + test "sends a JSON null assigneeId and returns the updated issue" do + issue = struct!(LinearCli.Linear.Issue, id: "i1", identifier: "CRY-1") + + Req.Test.stub(LinearCli.Api, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + %{"variables" => %{"id" => id, "input" => input}} = Jason.decode!(body) + + assert id == "CRY-1" + assert input == %{"assigneeId" => nil} + + Req.Test.json(conn, %{ + "data" => %{ + "issueUpdate" => %{ + "issue" => %{ + "id" => "i1", + "identifier" => "CRY-1", + "title" => "Fix it", + "branchName" => "cry-1-fix-it", + "description" => nil, + "assignee" => nil, + "team" => %{"id" => "t1", "key" => "ENG", "name" => "Engineering"}, + "comments" => %{"nodes" => []} + } + } + } + }) + end) + + assert {:ok, updated} = Linear.unassign_issue(issue) + assert updated.assignee == nil + end + + test "surfaces a GraphQL error" do + issue = struct!(LinearCli.Linear.Issue, id: "i1", identifier: "CRY-1") + + Req.Test.stub(LinearCli.Api, fn conn -> + Req.Test.json(conn, %{"errors" => [%{"message" => "mutation denied"}]}) + end) + + assert {:error, %Ash.Error.Invalid{}} = Linear.unassign_issue(issue) + end + end + describe "attach_issue_to_project/2+" do test "sends projectId and returns the issue refetched via full_fields" do issue = struct!(LinearCli.Linear.Issue, id: "i1", identifier: "CRY-1") diff --git a/documents/ash-domain-erd.adoc b/documents/ash-domain-erd.adoc index b7249f9..22d4392 100644 --- a/documents/ash-domain-erd.adoc +++ b/documents/ash-domain-erd.adoc @@ -1,6 +1,6 @@ = {my-title} Tj Vanderpoel (bougyman) -:revdate: Aug 18, 2026 +:revdate: Sep 24, 2026 :my-title: Ash domain ERD: LinearCli.Linear :icons: font :env-github: @@ -33,7 +33,7 @@ not from Ash relationship declarations or database join logic. == ERD diagram -All eight resources and their GraphQL/nested-data associations. +All nine resources and their GraphQL/nested-data associations. Every edge is labeled *[nested]* to make explicit that these are not declared Ash relationships. Resource attributes that hold nested structs are shown with the target resource name as their type. @@ -381,6 +381,13 @@ manual-implementation module, and the Linear GraphQL operation it calls. | `Linear.Issue.Update.Assign` | `issueUpdate(id:, input: { assigneeId, stateId? })` via `Issue.Update.run/2` +| `Issue` +| `unassign_issue` +| `:unassign` +| update +| `Linear.Issue.Update.Unassign` +| `issueUpdate(id:, input: { assigneeId: null })` via `Issue.Update.run/2` + | `Issue` | `attach_issue_to_project` | `:attach_to_project` @@ -410,6 +417,13 @@ manual-implementation module, and the Linear GraphQL operation it calls. | `Linear.Issue.Update.SetPriority` | `issueUpdate(id:, input: { priority })` via `Issue.Update.run/2`. Argument: `priority` (integer 0–4: 0=None, 1=Urgent, 2=High, 3=Medium, 4=Low). Name-to-integer mapping lives in `CLI.Commands.Issues.Mutations.parse_priority/1`; the GraphQL response returns `priority` as `Float!`. +| `Issue` +| `update_issue_description` +| `:update_description` +| update +| `Linear.Issue.Update.UpdateDescription` +| `issueUpdate(id:, input: { description })` via `Issue.Update.run/2` + | `IssueRelation` | `issue_relations` | `:list` @@ -475,9 +489,10 @@ manual-implementation module, and the Linear GraphQL operation it calls. `app/lib/linear_cli/linear/issue.ex` — not an Ash module; a plain module with a single `run/2` function. -All four issue-update actions (`:assign`, `:attach_to_project`, `:close`, -`:set_status`) delegate to this shared runner rather than each building -their own `issueUpdate` mutation. `run/2` calls the mutation, receives +All seven issue-update actions delegate to this shared runner rather than each +building its own `issueUpdate` mutation. The actions are `:assign`, +`:unassign`, `:attach_to_project`, `:close`, `:set_status`, +`:update_description`, and `:set_priority`. `run/2` calls the mutation, receives the updated issue map, and decodes it via `Issue.from_map/1` using `Issue.full_fields/0` (the full fragment including assignee, team, comments, and labels). From 78f8beebef2ae175ea3968ce4e5a1cdfea3b8950 Mon Sep 17 00:00:00 2001 From: bougyman's bot Date: Fri, 25 Sep 2026 09:33:34 -0400 Subject: [PATCH 2/2] chore(deps): update ash security fix --- app/mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/mix.lock b/app/mix.lock index 5b63d7d..6e11425 100644 --- a/app/mix.lock +++ b/app/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.33.9", "434d424f19f4d896382e51a9cc5d78bcfc197a926a6acbaa05b1a3ca89b4ad10", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.14", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 1.0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.6.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.3", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0ce2e1093e960917815824a71b4ffaa0d08fb6c4cecd314bc40ca49eced299a9"}, + "ash": {:hex, :ash, "3.33.11", "4118ddcab0b6c3c388fa7f69cc1fe5850f550bb7c26307b34f7a642f73ae4bf2", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.14", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 1.0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.6.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.3", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f36bdbab6ccc07029d09e4a8b75f9f4a1c0f14bc62db68cc4dd7eefa16c3b8b9"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "burrito": {:hex, :burrito, "1.6.0", "7af0a75f11680e8a6e9c01370c9af51cb9d0e15b3226eddf4f438dbc68570520", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:req, ">= 0.5.0", [hex: :req, repo: "hexpm", optional: false]}, {:typed_struct, "~> 0.2.0 or ~> 0.3.0", [hex: :typed_struct, repo: "hexpm", optional: false]}], "hexpm", "e636a00b032c45a69ff755d9fc53fa5fdc9e1d21bdbd229075fe4a15b05355fe"}, "castore": {:hex, :castore, "1.0.21", "0a0e8330dc267a40a3b7ad86d39302764bb71758172904e6a59d5ad6443ce307", [:mix], [], "hexpm", "e42e22723e25dbd46876d056a03f685513d6e98f6b5e555dc551321decd76c5c"}, @@ -19,7 +19,7 @@ "finch": {:hex, :finch, "0.23.0", "e3f9287ac25a8832f848b144c2b57346aac65b205e2e0629a52adfe6507fd837", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.8", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "80e58d3f936f57e3fdf404f83a3642897ae6d9fb642934e46da4d8fe761b99d5"}, "glob_ex": {:hex, :glob_ex, "0.1.12", "7b2d9369c20e2697efcfd185d13d6e84c94cd3bfd2730fbde613141c2e015c00", [:mix], [], "hexpm", "2e2fac83f113514434c7eaf267b4c38af2f91766f1cab2c5db7053b7fc1ee0bb"}, "hex_core": {:hex, :hex_core, "0.19.0", "b9b5d3cde2d5227b85c62c4ab9f9db586e6b38cf65a9bf5b683757cae9ced5eb", [:rebar3], [], "hexpm", "c2cc414f3893a7edbcab28207cadca13829407abe41f689c82f379fe22f026a5"}, - "hpax": {:hex, :hpax, "1.0.4", "777de5d433b0fbdc7c418159c8055910faa8047ffdb3d6b31098d2a46cd7685c", [:mix], [], "hexpm", "afc7cb142ebcc2d01ce7816190b98ce5dd49e799111b24249f3443d730f377ca"}, + "hpax": {:hex, :hpax, "1.1.0", "782931867cc23217c68fb5f68fe1a11f5e7544c7fda82c8a7019a5df5a4a1cdf", [:mix], [], "hexpm", "0b8d0f05832f55571d65ac720f79bf8994138ffbb133209dc4685eae0ad456a8"}, "igniter": {:hex, :igniter, "0.8.4", "f79f1bbdc2fb7b9ca030a22d12a585b060cbf5b94b9d3f23b1148578a9e05d11", [:mix], [{:ex_ast, "~> 0.5", [hex: :ex_ast, repo: "hexpm", optional: false]}, {:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4.5", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "a9b1cbec996ccb100b4f7d8130129b2dd3f18eb4224ac9a0e907e428ca90dbd7"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"},