From 4bc908377df28ce4b46f707da040435e1ac99f05 Mon Sep 17 00:00:00 2001 From: Zach Allaun Date: Fri, 15 Nov 2024 14:12:06 -0500 Subject: [PATCH] improvement: install from private hex repos with `repo/package` --- lib/igniter/project/deps.ex | 147 ++++++++++++++--------------- lib/igniter/util/install.ex | 6 +- lib/mix/tasks/igniter.install.ex | 2 + test/igniter/project/deps_test.exs | 28 ++++++ 4 files changed, 105 insertions(+), 78 deletions(-) diff --git a/lib/igniter/project/deps.ex b/lib/igniter/project/deps.ex index f5009d9..3a31fcd 100644 --- a/lib/igniter/project/deps.ex +++ b/lib/igniter/project/deps.ex @@ -261,93 +261,90 @@ defmodule Igniter.Project.Deps do @doc false def determine_dep_type_and_version(requirement) do - case String.split(requirement, "@", trim: true, parts: 2) do - [package] -> - if Regex.match?(~r/^[a-z][a-z0-9_]*$/, package) do - :inets.start() - :ssl.start() - url = ~c"https://hex.pm/api/packages/#{package}" - - case :httpc.request(:get, {url, [{~c"User-Agent", ~c"igniter-installer"}]}, [], []) do - {:ok, {{_version, _, _reason_phrase}, _headers, body}} -> - case Jason.decode(body) do - {:ok, - %{ - "releases" => releases - } = body} -> - case first_non_rc_version_or_first_version(releases, body) do - %{"version" => version} -> - {String.to_atom(package), - Igniter.Util.Version.version_string_to_general_requirement!(version)} - - _ -> - :error - end - - _ -> - :error - end + [package | maybe_version] = String.split(requirement, "@", trim: true, parts: 2) - _ -> - :error - end - else - :error - end + {package, opts} = + case String.split(package, "/", parts: 2) do + [repo, package] -> + {package, repo: repo} - [package, version] -> - case version do - "git:" <> requirement -> - if String.contains?(requirement, "@") do - case String.split(requirement, ["@"], trim: true) do - [url, ref] -> - [git: url, ref: ref, override: true] + [package] -> + {package, []} + end - _ -> - :error - end - else - [git: requirement, override: true] - end + case maybe_version do + [] -> + with {:ok, version} <- fetch_latest_version(package) do + {version, []} + end - "github:" <> requirement -> - if String.contains?(requirement, "@") do - case String.split(requirement, ["/", "@"], trim: true) do - [org, project, ref] -> - [github: "#{org}/#{project}", ref: ref, override: true] + ["git:" <> requirement] -> + {nil, git_dep_opts(requirement, :git)} - _ -> - :error - end - else - [github: requirement, override: true] - end + ["github:" <> requirement] -> + {nil, git_dep_opts(requirement, :github)} - "path:" <> requirement -> - [path: requirement, override: true] + ["path:" <> requirement] -> + {nil, path: requirement, override: true} - version -> - case Version.parse(version) do - {:ok, version} -> - "== #{version}" + [version] -> + case Version.parse(version) do + {:ok, version} -> + {"== #{version}", []} - _ -> - case Igniter.Util.Version.version_string_to_general_requirement(version) do - {:ok, requirement} -> - requirement + _ -> + case Igniter.Util.Version.version_string_to_general_requirement(version) do + {:ok, requirement} -> + {requirement, []} - _ -> - :error - end + _ -> + :error end end - |> case do - :error -> - :error - requirement -> - {String.to_atom(package), requirement} - end + _ -> + :error + end + |> case do + {version, additional_opts} -> + to_dependency_spec(package, version, additional_opts ++ opts) + + :error -> + :error + end + end + + defp to_dependency_spec(package, version, opts) + defp to_dependency_spec(package, version, []), do: {String.to_atom(package), version} + defp to_dependency_spec(package, nil, opts), do: {String.to_atom(package), opts} + defp to_dependency_spec(package, version, opts), do: {String.to_atom(package), version, opts} + + defp git_dep_opts(string, kind) do + case String.split(string, "@", trim: true) do + [git_dep, ref] -> + [{kind, git_dep}, {:ref, ref}, {:override, true}] + + [git_dep] -> + [{kind, git_dep}, {:override, true}] + end + end + + defp fetch_latest_version(package) do + if Regex.match?(~r/^[a-z][a-z0-9_]*$/, package) do + :inets.start() + :ssl.start() + url = ~c"https://hex.pm/api/packages/#{package}" + + with {:ok, {{_version, _, _reason_phrase}, _headers, body}} <- + :httpc.request(:get, {url, [{~c"User-Agent", ~c"igniter-installer"}]}, [], []), + {:ok, %{"releases" => releases} = body} <- Jason.decode(body), + %{"version" => version} <- first_non_rc_version_or_first_version(releases, body) do + {:ok, Igniter.Util.Version.version_string_to_general_requirement!(version)} + else + _ -> :error + end + else + :error end end diff --git a/lib/igniter/util/install.ex b/lib/igniter/util/install.ex index d702032..65600ce 100644 --- a/lib/igniter/util/install.ex +++ b/lib/igniter/util/install.ex @@ -27,11 +27,11 @@ defmodule Igniter.Util.Install do deps = Enum.map(deps, fn dep -> case Igniter.Project.Deps.determine_dep_type_and_version(dep) do - {install, requirement} -> - {install, requirement} - :error -> raise "Could not determine source for requested package #{dep}" + + dep_specification -> + dep_specification end end) diff --git a/lib/mix/tasks/igniter.install.ex b/lib/mix/tasks/igniter.install.ex index 4aba3cf..2ece119 100644 --- a/lib/mix/tasks/igniter.install.ex +++ b/lib/mix/tasks/igniter.install.ex @@ -16,6 +16,8 @@ defmodule Mix.Tasks.Igniter.Install do * `package@git:git_url` - The package will be installed from the specified git url. * `package@github:project/repo` - The package will be installed from the specified github repo. * `package@path:path/to/dep` - The package will be installed from the specified path. + * `repo/package` - The package will be installed from a private Hex repo. This can be used + along with all the options above, e.g. `repo/package@version`. ## Switches diff --git a/test/igniter/project/deps_test.exs b/test/igniter/project/deps_test.exs index f6c8808..57b614a 100644 --- a/test/igniter/project/deps_test.exs +++ b/test/igniter/project/deps_test.exs @@ -43,4 +43,32 @@ defmodule Igniter.Project.DepsTest do """) end end + + describe "determine_dep_type_and_version/1" do + test "parses to a version" do + tests = [ + "dep@1.0.0": {:dep, "== 1.0.0"}, + "dep@1.0": {:dep, "~> 1.0"}, + "dep@git:git_url": {:dep, git: "git_url", override: true}, + "dep@git:git_url@ref": {:dep, git: "git_url", ref: "ref", override: true}, + "dep@github:user/repo": {:dep, github: "user/repo", override: true}, + "dep@github:user/repo@ref": {:dep, github: "user/repo", ref: "ref", override: true}, + "dep@path:path/to/dep": {:dep, path: "path/to/dep", override: true}, + "repo/dep@1.0.0": {:dep, "== 1.0.0", repo: "repo"}, + "repo/dep@1.0": {:dep, "~> 1.0", repo: "repo"}, + "repo/dep@git:git_url": {:dep, git: "git_url", override: true, repo: "repo"}, + "repo/dep@git:git_url@ref": + {:dep, git: "git_url", ref: "ref", override: true, repo: "repo"}, + "repo/dep@github:user/repo": {:dep, github: "user/repo", override: true, repo: "repo"}, + "repo/dep@github:user/repo@ref": + {:dep, github: "user/repo", ref: "ref", override: true, repo: "repo"}, + "repo/dep@path:path/to/dep": {:dep, path: "path/to/dep", override: true, repo: "repo"} + ] + + for {spec, expected} <- tests do + assert spec |> to_string() |> Igniter.Project.Deps.determine_dep_type_and_version() == + expected + end + end + end end