Packages

A global CLI tool to add dependencies to Elixir projects without manually editing mix.exs

Current section

Files

Jump to
mix_add_cli lib mix tasks add.ex
Raw

lib/mix/tasks/add.ex

defmodule Mix.Tasks.Add do
@moduledoc """
Add dependencies to your mix.exs file.
## Usage
mix add PACKAGE [VERSION] [OPTIONS]
## Examples
mix add phoenix
mix add phoenix "~> 1.7"
mix add jason "~> 1.4" --only dev,test
mix add my_lib --github username/my_lib
mix add my_lib --path ../my_lib
## Options
* `--version`, `-v` - Specify version constraint (default: latest from hex.pm)
* `--only` - Comma-separated list of environments (dev,test,prod)
* `--runtime` - Include in runtime (default: true)
* `--git` - Git repository URL
* `--github` - GitHub repository (username/repo format)
* `--path` - Local path dependency
* `--branch` - Git branch
* `--tag` - Git tag
* `--ref` - Git ref
* `--override` - Override existing dependency
* `--organization` - Hex organization
* `--repo` - Hex repository (default: hexpm)
"""
use Igniter.Mix.Task
@impl Igniter.Mix.Task
def info(_argv, _source) do
%Igniter.Mix.Task.Info{
example: "mix add phoenix \"~> 1.7\"",
positional: [:package, :version],
schema: [
only: :string,
runtime: :boolean,
git: :string,
github: :string,
path: :string,
branch: :string,
tag: :string,
ref: :string,
override: :boolean,
organization: :string,
repo: :string
],
aliases: [
v: :version
]
}
end
@impl Igniter.Mix.Task
def igniter(igniter) do
package = igniter.args.positional[:package]
if is_nil(package) do
Igniter.add_issue(igniter, "No package name provided. Usage: mix add PACKAGE [VERSION]")
else
add_dependency(igniter, package)
end
end
defp add_dependency(igniter, package) do
dep_spec = build_dep_spec(igniter, package)
opts = build_opts(igniter)
igniter
|> Igniter.Project.Deps.add_dep(dep_spec, opts)
|> maybe_fetch_deps(package)
end
defp build_dep_spec(igniter, package) do
package_atom = String.to_atom(package)
version = igniter.args.positional[:version]
base_spec =
cond do
path = igniter.args.options[:path] ->
{package_atom, path: path}
git = igniter.args.options[:git] ->
git_opts = build_git_opts(igniter.args.options)
{package_atom, git_opts ++ [git: git]}
github = igniter.args.options[:github] ->
git_opts = build_git_opts(igniter.args.options)
{package_atom, git_opts ++ [github: github]}
true ->
version = version || fetch_latest_version(package)
{package_atom, version || "~> 0.1"}
end
add_dep_options(base_spec, igniter.args.options)
end
defp build_git_opts(options) do
[]
|> maybe_add_opt(:branch, options[:branch])
|> maybe_add_opt(:tag, options[:tag])
|> maybe_add_opt(:ref, options[:ref])
end
defp maybe_add_opt(opts, _key, nil), do: opts
defp maybe_add_opt(opts, key, value), do: [{key, value} | opts]
defp add_dep_options({name, spec}, options) when is_list(spec) do
spec =
spec
|> maybe_add_opt(:override, options[:override])
|> maybe_add_opt(:organization, options[:organization])
|> maybe_add_opt(:repo, options[:repo])
|> maybe_add_runtime(options[:runtime])
|> maybe_add_only(options[:only])
{name, spec}
end
defp add_dep_options({name, version}, options) when is_binary(version) do
opts =
[]
|> maybe_add_opt(:override, options[:override])
|> maybe_add_opt(:organization, options[:organization])
|> maybe_add_opt(:repo, options[:repo])
|> maybe_add_runtime(options[:runtime])
|> maybe_add_only(options[:only])
if opts == [] do
{name, version}
else
{name, version, opts}
end
end
defp maybe_add_runtime(opts, nil), do: opts
defp maybe_add_runtime(opts, false), do: [{:runtime, false} | opts]
defp maybe_add_runtime(opts, true), do: opts
defp maybe_add_only(opts, nil), do: opts
defp maybe_add_only(opts, ""), do: opts
defp maybe_add_only(opts, envs) when is_binary(envs) do
env_atoms =
envs
|> String.split(",")
|> Enum.map(&String.trim/1)
|> Enum.map(&String.to_atom/1)
[{:only, env_atoms} | opts]
end
defp build_opts(igniter) do
if igniter.args.options[:override] do
[on_exists: :overwrite]
else
[on_exists: :skip]
end
end
defp fetch_latest_version(package) do
case fetch_hex_info(package) do
{:ok, version} -> version
{:error, _} -> nil
end
end
defp fetch_hex_info(package) do
url = "https://hex.pm/api/packages/#{package}"
case Req.get(url) do
{:ok, %{status: 200, body: body}} ->
latest = get_latest_release(body)
{:ok, "~> #{latest}"}
_ ->
{:error, :not_found}
end
rescue
_ -> {:error, :request_failed}
end
defp get_latest_release(%{"releases" => releases}) when is_list(releases) do
releases
|> Enum.reject(&(&1["retired"]))
|> Enum.map(&(&1["version"]))
|> Enum.sort(:desc)
|> List.first()
|> format_version()
end
defp get_latest_release(_), do: "0.1.0"
defp format_version(nil), do: "0.1.0"
defp format_version(version) do
# Extract major.minor for ~> constraint
case String.split(version, ".") do
[major, minor | _] -> "#{major}.#{minor}"
_ -> version
end
end
defp maybe_fetch_deps(igniter, package) do
igniter
|> Igniter.add_notice("""
Added #{package} to mix.exs
Run `mix deps.get` to fetch the dependency.
""")
end
end