dependabot-hex 0.88.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/helpers/build +19 -0
  3. data/helpers/deps/jason/.fetch +0 -0
  4. data/helpers/deps/jason/.hex +2 -0
  5. data/helpers/deps/jason/CHANGELOG.md +60 -0
  6. data/helpers/deps/jason/LICENSE +13 -0
  7. data/helpers/deps/jason/README.md +179 -0
  8. data/helpers/deps/jason/hex_metadata.config +20 -0
  9. data/helpers/deps/jason/lib/codegen.ex +158 -0
  10. data/helpers/deps/jason/lib/decoder.ex +657 -0
  11. data/helpers/deps/jason/lib/encode.ex +630 -0
  12. data/helpers/deps/jason/lib/encoder.ex +216 -0
  13. data/helpers/deps/jason/lib/formatter.ex +253 -0
  14. data/helpers/deps/jason/lib/fragment.ex +11 -0
  15. data/helpers/deps/jason/lib/helpers.ex +90 -0
  16. data/helpers/deps/jason/lib/jason.ex +228 -0
  17. data/helpers/deps/jason/mix.exs +92 -0
  18. data/helpers/lib/check_update.exs +92 -0
  19. data/helpers/lib/do_update.exs +39 -0
  20. data/helpers/lib/parse_deps.exs +103 -0
  21. data/helpers/lib/run.exs +76 -0
  22. data/helpers/mix.exs +21 -0
  23. data/helpers/mix.lock +3 -0
  24. data/lib/dependabot/hex.rb +11 -0
  25. data/lib/dependabot/hex/file_fetcher.rb +79 -0
  26. data/lib/dependabot/hex/file_parser.rb +125 -0
  27. data/lib/dependabot/hex/file_updater.rb +71 -0
  28. data/lib/dependabot/hex/file_updater/lockfile_updater.rb +142 -0
  29. data/lib/dependabot/hex/file_updater/mixfile_git_pin_updater.rb +51 -0
  30. data/lib/dependabot/hex/file_updater/mixfile_requirement_updater.rb +72 -0
  31. data/lib/dependabot/hex/file_updater/mixfile_sanitizer.rb +26 -0
  32. data/lib/dependabot/hex/file_updater/mixfile_updater.rb +94 -0
  33. data/lib/dependabot/hex/metadata_finder.rb +70 -0
  34. data/lib/dependabot/hex/native_helpers.rb +20 -0
  35. data/lib/dependabot/hex/requirement.rb +53 -0
  36. data/lib/dependabot/hex/update_checker.rb +275 -0
  37. data/lib/dependabot/hex/update_checker/file_preparer.rb +191 -0
  38. data/lib/dependabot/hex/update_checker/requirements_updater.rb +173 -0
  39. data/lib/dependabot/hex/update_checker/version_resolver.rb +170 -0
  40. data/lib/dependabot/hex/version.rb +67 -0
  41. metadata +208 -0
@@ -0,0 +1,39 @@
1
+ [dependency_name | credentials] = System.argv()
2
+
3
+ grouped_creds = Enum.reduce credentials, [], fn cred, acc ->
4
+ if List.last(acc) == nil || List.last(acc)[:token] do
5
+ List.insert_at(acc, -1, %{ organization: cred })
6
+ else
7
+ { item, acc } = List.pop_at(acc, -1)
8
+ item = Map.put(item, :token, cred)
9
+ List.insert_at(acc, -1, item)
10
+ end
11
+ end
12
+
13
+ Enum.each grouped_creds, fn cred ->
14
+ hexpm = Hex.Repo.get_repo("hexpm")
15
+ repo = %{
16
+ url: hexpm.url <> "/repos/#{cred.organization}",
17
+ public_key: nil,
18
+ auth_key: cred.token
19
+ }
20
+
21
+ Hex.Config.read()
22
+ |> Hex.Config.read_repos()
23
+ |> Map.put("hexpm:#{cred.organization}", repo)
24
+ |> Hex.Config.update_repos()
25
+ end
26
+
27
+ # dependency atom
28
+ dependency = String.to_atom(dependency_name)
29
+
30
+ # Fetch dependencies that needs updating
31
+ {dependency_lock, rest_lock} = Map.split(Mix.Dep.Lock.read(), [dependency])
32
+ Mix.Dep.Fetcher.by_name([dependency_name], dependency_lock, rest_lock, [])
33
+
34
+ lockfile_content =
35
+ "mix.lock"
36
+ |> File.read()
37
+ |> :erlang.term_to_binary()
38
+
39
+ IO.write(:stdio, lockfile_content)
@@ -0,0 +1,103 @@
1
+ defmodule Parser do
2
+ def run do
3
+ Mix.Dep.load_on_environment([])
4
+ |> Enum.flat_map(&parse_dep/1)
5
+ |> Enum.map(&build_dependency(&1.opts[:lock], &1))
6
+ end
7
+
8
+ defp build_dependency(nil, dep) do
9
+ %{
10
+ name: dep.app,
11
+ from: Path.relative_to_cwd(dep.from),
12
+ groups: [],
13
+ requirement: normalise_requirement(dep.requirement),
14
+ top_level: dep.top_level || umbrella_top_level_dep?(dep)
15
+ }
16
+ end
17
+
18
+ defp build_dependency(lock, dep) do
19
+ {version, checksum, source} = parse_lock(lock)
20
+ groups = parse_groups(dep.opts[:only])
21
+
22
+ %{
23
+ name: dep.app,
24
+ from: Path.relative_to_cwd(dep.from),
25
+ version: version,
26
+ groups: groups,
27
+ checksum: checksum,
28
+ requirement: normalise_requirement(dep.requirement),
29
+ source: source,
30
+ top_level: dep.top_level || umbrella_top_level_dep?(dep)
31
+ }
32
+ end
33
+
34
+ defp parse_groups(nil), do: []
35
+ defp parse_groups(only) when is_list(only), do: only
36
+ defp parse_groups(only), do: [only]
37
+
38
+ # path dependency
39
+ defp parse_dep(%{scm: Mix.SCM.Path, opts: opts} = dep) do
40
+ cond do
41
+ # umbrella dependency - ignore
42
+ opts[:in_umbrella] ->
43
+ []
44
+
45
+ # umbrella application
46
+ opts[:from_umbrella] ->
47
+ Enum.reject(dep.deps, fn dep -> dep.opts[:in_umbrella] end)
48
+
49
+ true ->
50
+ []
51
+ end
52
+ end
53
+
54
+ # hex, git dependency
55
+ defp parse_dep(%{scm: scm} = dep) when scm in [Hex.SCM, Mix.SCM.Git], do: [dep]
56
+
57
+ # unsupported
58
+ defp parse_dep(_dep), do: []
59
+
60
+ defp umbrella_top_level_dep?(dep) do
61
+ if Mix.Project.umbrella?() do
62
+ apps_paths = Path.expand(Mix.Project.config()[:apps_path], File.cwd!())
63
+ String.contains?(Path.dirname(Path.dirname(dep.from)), apps_paths)
64
+ else
65
+ false
66
+ end
67
+ end
68
+
69
+ defp parse_lock({:git, repo_url, checksum, opts}),
70
+ do: {nil, checksum, git_source(repo_url, opts)}
71
+
72
+ defp parse_lock({:hex, _app, version, checksum, _managers, _dependencies, _source}),
73
+ do: {version, checksum, nil}
74
+
75
+ defp parse_lock({:hex, _app, version, checksum, _managers, _dependencies}),
76
+ do: {version, checksum, nil}
77
+
78
+ defp normalise_requirement(req) do
79
+ req
80
+ |> maybe_regex_to_str()
81
+ |> empty_str_to_nil()
82
+ end
83
+
84
+ defp maybe_regex_to_str(s), do: if Regex.regex?(s), do: Regex.source(s), else: s
85
+ defp empty_str_to_nil(""), do: nil
86
+ defp empty_str_to_nil(s), do: s
87
+
88
+ def git_source(repo_url, opts) do
89
+ ref = opts[:ref] || opts[:tag]
90
+ ref = if is_list(ref), do: to_string(ref), else: ref
91
+
92
+ %{
93
+ type: "git",
94
+ url: repo_url,
95
+ branch: opts[:branch] || "master",
96
+ ref: ref
97
+ }
98
+ end
99
+ end
100
+
101
+ dependencies = :erlang.term_to_binary({:ok, Parser.run()})
102
+
103
+ IO.write(:stdio, dependencies)
@@ -0,0 +1,76 @@
1
+ defmodule DependencyHelper do
2
+ def main() do
3
+ IO.read(:stdio, :all)
4
+ |> Jason.decode!()
5
+ |> run()
6
+ |> case do
7
+ {output, 0} ->
8
+ if output =~ "No authenticated organization found" do
9
+ {:error, output}
10
+ else
11
+ {:ok, :erlang.binary_to_term(output)}
12
+ end
13
+
14
+ {error, 1} -> {:error, error}
15
+ end
16
+ |> handle_result()
17
+ end
18
+
19
+ defp handle_result({:ok, {:ok, result}}) do
20
+ encode_and_write(%{"result" => result})
21
+ end
22
+
23
+ defp handle_result({:ok, {:error, reason}}) do
24
+ encode_and_write(%{"error" => reason})
25
+ System.halt(1)
26
+ end
27
+
28
+ defp handle_result({:error, reason}) do
29
+ encode_and_write(%{"error" => reason})
30
+ System.halt(1)
31
+ end
32
+
33
+ defp encode_and_write(content) do
34
+ content
35
+ |> Jason.encode!()
36
+ |> IO.write()
37
+ end
38
+
39
+ defp run(%{"function" => "parse", "args" => [dir]}) do
40
+ run_script("parse_deps.exs", dir)
41
+ end
42
+
43
+ defp run(%{"function" => "get_latest_resolvable_version", "args" => [dir, dependency_name, credentials]}) do
44
+ run_script("check_update.exs", dir, [dependency_name] ++ credentials)
45
+ end
46
+
47
+ defp run(%{"function" => "get_updated_lockfile", "args" => [dir, dependency_name, credentials]}) do
48
+ run_script("do_update.exs", dir, [dependency_name] ++ credentials)
49
+ end
50
+
51
+ defp run_script(script, dir, args \\ []) do
52
+ args = [
53
+ "run",
54
+ "--no-deps-check",
55
+ "--no-start",
56
+ "--no-compile",
57
+ "--no-elixir-version-check",
58
+ script
59
+ ] ++ args
60
+
61
+ System.cmd(
62
+ "mix",
63
+ args,
64
+ [
65
+ cd: dir,
66
+ env: %{
67
+ "MIX_EXS" => nil,
68
+ "MIX_LOCK" => nil,
69
+ "MIX_DEPS" => nil
70
+ }
71
+ ]
72
+ )
73
+ end
74
+ end
75
+
76
+ DependencyHelper.main()
@@ -0,0 +1,21 @@
1
+ defmodule DependabotCore.Mixfile do
2
+ use Mix.Project
3
+
4
+ def project do
5
+ [app: :dependabot_core,
6
+ version: "0.1.0",
7
+ elixir: "~> 1.5",
8
+ start_permanent: Mix.env == :prod,
9
+ lockfile: System.get_env("MIX_LOCK") || "mix.lock",
10
+ deps_path: System.get_env("MIX_DEPS") || "deps",
11
+ deps: deps()]
12
+ end
13
+
14
+ def application do
15
+ [extra_applications: [:logger]]
16
+ end
17
+
18
+ defp deps() do
19
+ [{:jason, "~> 1.0-rc"}]
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ %{
2
+ "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
3
+ }
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # These all need to be required so the various classes can be registered in a
4
+ # lookup table of package manager names to concrete classes.
5
+ require "dependabot/hex/file_fetcher"
6
+ require "dependabot/hex/file_parser"
7
+ require "dependabot/hex/update_checker"
8
+ require "dependabot/hex/file_updater"
9
+ require "dependabot/hex/metadata_finder"
10
+ require "dependabot/hex/requirement"
11
+ require "dependabot/hex/version"
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/file_fetchers"
4
+ require "dependabot/file_fetchers/base"
5
+
6
+ module Dependabot
7
+ module Hex
8
+ class FileFetcher < Dependabot::FileFetchers::Base
9
+ APPS_PATH_REGEX = /apps_path:\s*"(?<path>.*?)"/m.freeze
10
+ STRING_ARG = %{(?:["'](.*?)["'])}
11
+ EVAL_FILE = /Code\.eval_file\(#{STRING_ARG}(?:\s*,\s*#{STRING_ARG})?\)/.
12
+ freeze
13
+
14
+ def self.required_files_in?(filenames)
15
+ filenames.include?("mix.exs")
16
+ end
17
+
18
+ def self.required_files_message
19
+ "Repo must contain a mix.exs."
20
+ end
21
+
22
+ private
23
+
24
+ def fetch_files
25
+ fetched_files = []
26
+ fetched_files << mixfile
27
+ fetched_files << lockfile if lockfile
28
+ fetched_files += subapp_mixfiles
29
+ fetched_files += evaled_files
30
+ fetched_files
31
+ end
32
+
33
+ def mixfile
34
+ @mixfile ||= fetch_file_from_host("mix.exs")
35
+ end
36
+
37
+ def lockfile
38
+ return @lockfile if @lockfile_lookup_attempted
39
+
40
+ @lockfile_lookup_attempted = true
41
+ @lockfile ||= fetch_file_from_host("mix.lock")
42
+ rescue Dependabot::DependencyFileNotFound
43
+ nil
44
+ end
45
+
46
+ def subapp_mixfiles
47
+ apps_path = mixfile.content.match(APPS_PATH_REGEX)&.
48
+ named_captures&.fetch("path")
49
+ return [] unless apps_path
50
+
51
+ app_directories = repo_contents(dir: apps_path).
52
+ select { |f| f.type == "dir" }
53
+
54
+ app_directories.map do |dir|
55
+ fetch_file_from_host("#{dir.path}/mix.exs")
56
+ rescue Dependabot::DependencyFileNotFound
57
+ # If the folder doesn't have a mix.exs it *might* be because it's
58
+ # not an app. Ignore the fact we couldn't fetch one and proceed with
59
+ # updating (it will blow up later if there are problems)
60
+ nil
61
+ end.compact
62
+ rescue Octokit::NotFound, Gitlab::Error::NotFound
63
+ # If the path specified in apps_path doesn't exist then it's not being
64
+ # used. We can just return an empty array of subapp files.
65
+ []
66
+ end
67
+
68
+ def evaled_files
69
+ mixfile.content.scan(EVAL_FILE).map do |eval_file_args|
70
+ path = Pathname.new(File.join(*eval_file_args.reverse)).
71
+ cleanpath.to_path
72
+ fetch_file_from_host(path).tap { |f| f.support_file = true }
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ Dependabot::FileFetchers.register("hex", Dependabot::Hex::FileFetcher)
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/dependency"
4
+ require "dependabot/file_parsers"
5
+ require "dependabot/file_parsers/base"
6
+ require "dependabot/hex/native_helpers"
7
+ require "dependabot/shared_helpers"
8
+ require "dependabot/errors"
9
+
10
+ # For docs, see https://hexdocs.pm/mix/Mix.Tasks.Deps.html
11
+ module Dependabot
12
+ module Hex
13
+ class FileParser < Dependabot::FileParsers::Base
14
+ require "dependabot/file_parsers/base/dependency_set"
15
+
16
+ def parse
17
+ dependency_set = DependencySet.new
18
+
19
+ dependency_details.each do |dep|
20
+ git_dependency = dep["source"]&.fetch("type") == "git"
21
+
22
+ dependency_set <<
23
+ Dependency.new(
24
+ name: dep["name"],
25
+ version: git_dependency ? dep["checksum"] : dep["version"],
26
+ requirements: [{
27
+ requirement: dep["requirement"],
28
+ groups: dep["groups"],
29
+ source: dep["source"] && symbolize_keys(dep["source"]),
30
+ file: dep["from"]
31
+ }],
32
+ package_manager: "hex"
33
+ )
34
+ end
35
+
36
+ dependency_set.dependencies.sort_by(&:name)
37
+ end
38
+
39
+ private
40
+
41
+ def dependency_details
42
+ SharedHelpers.in_a_temporary_directory do
43
+ write_sanitized_mixfiles
44
+ write_supporting_files
45
+ File.write("mix.lock", lockfile.content) if lockfile
46
+ FileUtils.cp(elixir_helper_parse_deps_path, "parse_deps.exs")
47
+
48
+ SharedHelpers.run_helper_subprocess(
49
+ env: mix_env,
50
+ command: "mix run #{elixir_helper_path}",
51
+ function: "parse",
52
+ args: [Dir.pwd],
53
+ popen_opts: { err: %i(child out) }
54
+ )
55
+ end
56
+ rescue Dependabot::SharedHelpers::HelperSubprocessFailed => error
57
+ result_json =
58
+ error.message.lines.
59
+ drop_while { |l| !l.start_with?('{"result":') }.
60
+ join
61
+
62
+ raise DependencyFileNotEvaluatable, error.message if result_json.empty?
63
+
64
+ JSON.parse(result_json).fetch("result")
65
+ end
66
+
67
+ def write_sanitized_mixfiles
68
+ mixfiles.each do |file|
69
+ path = file.name
70
+ FileUtils.mkdir_p(Pathname.new(path).dirname)
71
+ File.write(path, sanitize_mixfile(file.content))
72
+ end
73
+ end
74
+
75
+ def write_supporting_files
76
+ dependency_files.select(&:support_file).each do |file|
77
+ path = file.name
78
+ FileUtils.mkdir_p(Pathname.new(path).dirname)
79
+ File.write(path, file.content)
80
+ end
81
+ end
82
+
83
+ def sanitize_mixfile(content)
84
+ content.
85
+ gsub(/File\.read!\(.*?\)/, '"0.0.1"').
86
+ gsub(/File\.read\(.*?\)/, '{:ok, "0.0.1"}')
87
+ end
88
+
89
+ def mix_env
90
+ {
91
+ "MIX_EXS" => File.join(NativeHelpers.hex_helpers_dir, "mix.exs"),
92
+ "MIX_LOCK" => File.join(NativeHelpers.hex_helpers_dir, "mix.lock"),
93
+ "MIX_DEPS" => File.join(NativeHelpers.hex_helpers_dir, "deps"),
94
+ "MIX_QUIET" => "1"
95
+ }
96
+ end
97
+
98
+ def elixir_helper_path
99
+ File.join(NativeHelpers.hex_helpers_dir, "lib/run.exs")
100
+ end
101
+
102
+ def elixir_helper_parse_deps_path
103
+ File.join(NativeHelpers.hex_helpers_dir, "lib/parse_deps.exs")
104
+ end
105
+
106
+ def check_required_files
107
+ raise "No mixfile!" if mixfiles.none?
108
+ end
109
+
110
+ def symbolize_keys(hash)
111
+ Hash[hash.keys.map { |k| [k.to_sym, hash[k]] }]
112
+ end
113
+
114
+ def mixfiles
115
+ dependency_files.select { |f| f.name.end_with?("mix.exs") }
116
+ end
117
+
118
+ def lockfile
119
+ @lockfile ||= get_original_file("mix.lock")
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ Dependabot::FileParsers.register("hex", Dependabot::Hex::FileParser)