1defmodule Mix.Tasks.Hex.Config do 2 use Mix.Task 3 4 @shortdoc "Reads, updates or deletes local Hex config" 5 6 @moduledoc """ 7 Reads, updates or deletes local Hex config. 8 9 ## List config keys and values 10 11 $ mix hex.config 12 13 ## Get or delete config value for KEY 14 15 $ mix hex.config KEY [--delete] 16 17 ## Set config KEY to VALUE 18 19 $ mix hex.config KEY VALUE 20 21 ## Config keys 22 23 * `api_key` - Your API key. If you are authenticated this config will override 24 the API key used for your authenticated user. Can be also be overridden by 25 setting the environment variable `HEX_API_KEY` 26 * `api_url` - Hex API URL. Can be overridden by setting the environment 27 variable `HEX_API_URL` (Default: `#{inspect(Hex.State.default_api_url())}`) 28 * `offline` - If set to true Hex will not fetch the registry or packages and 29 will instead use locally cached files if they are available. Can be 30 overridden by setting the environment variable `HEX_OFFLINE` (Default: 31 `false`) 32 * `unsafe_https` - If set to true Hex will not verify HTTPS certificates. 33 Can be overridden by setting the environment variable `HEX_UNSAFE_HTTPS` 34 (Default: `false`) 35 * `unsafe_registry` - If set to true Hex will not verify the registry 36 signature against the repository's public key. Can be overridden by 37 setting the environment variable `HEX_UNSAFE_REGISTRY` (Default: 38 `false`) 39 * `no_verify_repo_origin` - If set to true Hex will not verify the registry 40 origin. Can be overridden by setting the environment variable 41 `HEX_NO_VERIFY_REPO_ORIGIN` (Default: `false`) 42 * `http_proxy` - HTTP proxy server. Can be overridden by setting the 43 environment variable `HTTP_PROXY` (Default: `nil`) 44 * `https_proxy` - HTTPS proxy server. Can be overridden by setting the 45 environment variable `HTTPS_PROXY` (Default: `nil`) 46 * `no_proxy` - A comma separated list of hostnames that will not be proxied, 47 asterisks can be used as wildcards. Can be overridden by setting the 48 environment variable `no_proxy` or `NO_PROXY` (Default: `nil`) 49 * `http_concurrency` - Limits the number of concurrent HTTP requests in 50 flight. Can be overridden by setting the environment variable 51 `HEX_HTTP_CONCURRENCY` (Default: `8`) 52 * `http_timeout` - Sets the timeout for HTTP requests in seconds. Can be 53 overridden by setting the environment variable `HEX_HTTP_TIMEOUT` 54 (Default: `nil`) 55 * `mirror_url` - Hex mirror URL. Can be overridden by setting the 56 environment variable `HEX_MIRROR` (Default: `nil`) 57 * `cacerts_path` - Path to the CA certificate store PEM file. If not set, 58 a CA bundle that ships with Hex is used. Can be overridden by setting the 59 environment variable `HEX_CACERTS_PATH`. (Default: `nil`) 60 * `no_short_urls` - If set to true Hex will not 61 shorten any links. Can be overridden by setting the environment variable 62 `HEX_NO_SHORT_URLS` (Default: `false`) 63 64 Hex responds to these additional environment variables: 65 66 * `HEX_HOME` - directory where Hex stores the cache and configuration 67 (Default: `~/.hex`) 68 69 * `MIX_XDG` - asks Hex to follow the [XDG Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) 70 for its home directory and configuration files. `HEX_HOME` has higher preference 71 than `MIX_XDG`. If none of the variables are set, the default directory 72 `~/.hex` will be used. 73 74 ## Config overrides 75 76 All keys from the "Config keys" section above can be overridden. 77 78 Hex uses the following order of precedence when computing a value for a given key: 79 80 1. System environment 81 82 Setting for example `HEX_API_URL` environment variable has always the 83 highest precedence for the `api_url` config key. 84 85 2. Project configuration 86 87 Hex allows an optional, per-project configuration in the `mix.exs` file. 88 89 For example, to override `api_url` config key, add the following: 90 91 # mix.exs 92 defmodule MyApp.MixProject 93 def project() do 94 [ 95 # ... 96 deps: deps(), 97 hex: hex() 98 ] 99 end 100 101 defp hex() do 102 [ 103 api_url: "https://hex.myorg/api" 104 ] 105 end 106 end 107 108 3. Global configuration using `mix hex.config KEY VALUE` 109 110 4. Default value 111 112 ## Command line options 113 114 * `--delete` - Remove a specific config key 115 """ 116 @behaviour Hex.Mix.TaskDescription 117 118 @switches [delete: :boolean] 119 120 @impl true 121 def run(args) do 122 Hex.start() 123 {opts, args} = Hex.OptionParser.parse!(args, strict: @switches) 124 125 case args do 126 [] -> 127 list() 128 129 ["$" <> _key | _] -> 130 Mix.raise("Invalid key name") 131 132 [key] -> 133 if opts[:delete] do 134 delete(key) 135 else 136 read(key) 137 end 138 139 [key, value] -> 140 set(key, value) 141 142 _ -> 143 Mix.raise(""" 144 Invalid arguments, expected: 145 146 mix hex.config KEY [VALUE] 147 """) 148 end 149 end 150 151 @impl true 152 def tasks() do 153 [ 154 {"", "Reads, updates or deletes local Hex config"}, 155 {"KEY", "Get config value for KEY"}, 156 {"KEY --delete", "Delete config value for KEY"}, 157 {"KEY VALUE", "Set config KEY to VALUE"} 158 ] 159 end 160 161 defp list() do 162 Enum.each(valid_read_keys(), fn {config, _internal} -> 163 read(config, true) 164 end) 165 end 166 167 defp read(key, verbose \\ false) 168 169 defp read(key, verbose) when is_binary(key) do 170 key = String.to_atom(key) 171 172 case Keyword.fetch(valid_read_keys(), key) do 173 {:ok, internal} -> 174 fetch_current_value_and_print(internal, key, verbose) 175 176 _error -> 177 Mix.raise("The key #{key} is not valid") 178 end 179 end 180 181 defp read(key, verbose) when is_atom(key), do: read(to_string(key), verbose) 182 183 defp fetch_current_value_and_print(internal, key, verbose) do 184 case Map.fetch(Hex.State.get_all(), internal) do 185 {:ok, {{:env, env_var}, value}} -> 186 print_value(key, value, verbose, "(using `#{env_var}`)") 187 188 {:ok, {{:global_config, _key}, value}} -> 189 print_value(key, value, verbose, "(using `#{config_path()}`)") 190 191 {:ok, {{:project_config, _key}, value}} -> 192 print_value(key, value, verbose, "(using `mix.exs`)") 193 194 {:ok, {{:env_path_join, {env_var, _prefix}}, value}} -> 195 print_value(key, value, verbose, "(using `#{env_var}`)") 196 197 {:ok, {kind, value}} when kind in [:default, :computed] -> 198 print_value(key, value, verbose, "(default)") 199 200 :error -> 201 Mix.raise("Config does not contain the key #{key}") 202 end 203 end 204 205 defp print_value(key, value, true, source), 206 do: Hex.Shell.info("#{key}: #{inspect(value, pretty: true)} #{source}") 207 208 defp print_value(_key, value, false, _source), do: Hex.Shell.info(inspect(value, pretty: true)) 209 210 defp delete(key) do 211 key = String.to_atom(key) 212 213 if Keyword.has_key?(valid_write_keys(), key) do 214 Hex.Config.remove([key]) 215 end 216 end 217 218 defp set(key, value) do 219 key = String.to_atom(key) 220 221 if Keyword.has_key?(valid_write_keys(), key) do 222 Hex.Config.update([{key, value}]) 223 else 224 Mix.raise("Invalid key #{key}") 225 end 226 end 227 228 defp config_path() do 229 :config_home 230 |> Hex.State.fetch!() 231 |> Path.join("hex.config") 232 end 233 234 defp valid_keys() do 235 Enum.map(Hex.State.config(), fn {internal, map} -> 236 [config | _] = Map.get(map, :config, [nil]) 237 [env | _] = Map.get(map, :env, [nil]) 238 239 cond do 240 String.starts_with?(to_string(config), "$") -> {internal, config, :not_accessible} 241 is_nil(config) and not is_nil(env) -> {internal, config, :env_only} 242 is_nil(config) and is_nil(env) -> {internal, config, :read_only} 243 true -> {internal, config, :read_and_write} 244 end 245 end) 246 end 247 248 defp valid_read_keys() do 249 valid_keys() 250 |> Enum.map(fn {internal, config, access} -> 251 if access != :not_accessible, do: key_representation(internal, config) 252 end) 253 |> Enum.filter(&(&1 != nil)) 254 end 255 256 defp valid_write_keys() do 257 valid_keys() 258 |> Enum.map(fn {internal, config, access} -> 259 if access == :read_and_write, do: key_representation(internal, config) 260 end) 261 |> Enum.filter(&(&1 != nil)) 262 end 263 264 defp key_representation(internal, nil), do: {internal, internal} 265 defp key_representation(internal, config), do: {config, internal} 266end 267