├── .github ├── FUNDING.yml ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── main.yml ├── test ├── test_helper.exs └── octo_fetch_test.exs ├── coveralls.json ├── guides └── images │ ├── your_logo_here.png │ └── logo.svg ├── .formatter.exs ├── config └── config.exs ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── lib ├── octo_fetch │ ├── test.ex │ └── downloader.ex └── octo_fetch.ex ├── mix.exs ├── README.md ├── mix.lock └── .credo.exs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /coveralls.json: -------------------------------------------------------------------------------- 1 | { 2 | "skip_files": ["test/"], 3 | "minimum_coverage": 75 4 | } 5 | -------------------------------------------------------------------------------- /guides/images/your_logo_here.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akoutmos/octo_fetch/HEAD/guides/images/your_logo_here.png -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | line_length: 120, 4 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 5 | ] 6 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | if Mix.env() != :prod do 4 | config :git_hooks, 5 | auto_install: true, 6 | verbose: true, 7 | hooks: [ 8 | pre_commit: [ 9 | tasks: [ 10 | {:cmd, "mix format --check-formatted"}, 11 | {:cmd, "mix compile --warnings-as-errors"}, 12 | {:cmd, "mix credo --strict"}, 13 | {:cmd, "mix dialyzer"}, 14 | {:cmd, "mix test"} 15 | ] 16 | ] 17 | ] 18 | end 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help me fix bugs and problems 4 | title: '[BUG]' 5 | labels: bug 6 | assignees: akoutmos 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior or a GitHub repo that reproduces the issue. 14 | 15 | **Expected behavior** 16 | A clear and concise description of what you expected to happen. 17 | 18 | **Environment** 19 | 20 | - Elixir version: 21 | - Erlang/OTP version: 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '[FEATURE]' 5 | labels: feature request 6 | assignees: akoutmos 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. 11 | 12 | **Describe the solution you would like to see** 13 | A clear and concise description of what you want to happen. 14 | 15 | **How would you expect this feature to work** 16 | A description of a possible API, behaviour, modules, etc. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Dialyzer PLT files 14 | /priv/plts/ 15 | 16 | # Ignore .fetch files in case you like to edit your project deps locally. 17 | /.fetch 18 | 19 | # If the VM crashes, it generates a dump, let's ignore it too. 20 | erl_crash.dump 21 | 22 | # Also ignore archive artifacts (built via "mix archive.build"). 23 | *.ez 24 | 25 | # Ignore package tarball (built via "mix hex.build"). 26 | octo_fetch-*.tar 27 | 28 | # Temporary files, for example, from tests. 29 | /tmp/ 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.4.0] - 2023-11-14 9 | 10 | ### Fixed 11 | 12 | - Fixed issue when extracting Zip files with directories [#2](https://github.com/akoutmos/octo_fetch/pull/2) 13 | 14 | ## [0.3.0] - 2023-03-30 15 | 16 | ### Changed 17 | 18 | - Relaxed the `:castore` version requirement 19 | 20 | ## [0.2.0] - 2022-10-08 21 | 22 | ### Added 23 | 24 | - Support for FreeBSD 25 | - `post_write_hook` callback that is invoked whenever a file is written 26 | - `pre_download_hook` callback that is invoked prior to starting a download 27 | 28 | ### Changed 29 | 30 | - Switched from `:macos` to `:darwin` 31 | 32 | ## [0.1.0] - 2022-10-08 33 | 34 | ### Added 35 | 36 | - Initial release 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Alexander Koutmos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: OctoFetch CI 2 | 3 | env: 4 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | static_analysis: 14 | name: Static Analysis 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v2 20 | - name: Set up Elixir 21 | uses: erlef/setup-beam@v1 22 | with: 23 | elixir-version: '1.14.0' 24 | otp-version: '25.0' 25 | - name: Restore cache 26 | uses: actions/cache@v3 27 | with: 28 | path: | 29 | deps 30 | _build 31 | priv/plts 32 | key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} 33 | restore-keys: | 34 | ${{ runner.os }}-mix- 35 | - name: Install dependencies 36 | run: mix deps.get 37 | - name: Mix Formatter 38 | run: mix format --check-formatted 39 | - name: Check for compiler warnings 40 | run: mix compile --warnings-as-errors 41 | - name: Credo strict checks 42 | run: mix credo --strict 43 | - name: Dialyzer checks 44 | run: mix dialyzer 45 | 46 | unit_test: 47 | name: Run ExUnit tests 48 | runs-on: ubuntu-latest 49 | 50 | strategy: 51 | matrix: 52 | elixir: 53 | - '1.14' 54 | otp: 55 | - '25.0' 56 | 57 | steps: 58 | - name: Checkout code 59 | uses: actions/checkout@v2 60 | - name: Set up Elixir 61 | uses: erlef/setup-beam@v1 62 | with: 63 | elixir-version: ${{ matrix.elixir }} 64 | otp-version: ${{ matrix.otp }} 65 | - name: Install dependencies 66 | run: mix deps.get 67 | - name: ExUnit tests 68 | env: 69 | MIX_ENV: test 70 | run: mix coveralls.github 71 | -------------------------------------------------------------------------------- /lib/octo_fetch/test.ex: -------------------------------------------------------------------------------- 1 | defmodule OctoFetch.Test do 2 | @moduledoc """ 3 | This module contains testing utilities to validate 4 | downloader modules and their implementations. 5 | """ 6 | 7 | import ExUnit.Assertions, only: [assert: 1] 8 | 9 | @doc """ 10 | Go through all of the supported downloads and validate that 11 | they work. 12 | """ 13 | def test_all_supported_downloads(downloader_module) do 14 | # Get all of the download versions 15 | download_versions = Keyword.fetch!(downloader_module.init_opts(), :download_versions) 16 | 17 | # Create a place to store the file downloads 18 | tmp_octo_fetch_dir = Path.join(System.tmp_dir!(), "/octo_fetch_test_downloads") 19 | File.mkdir_p!(tmp_octo_fetch_dir) 20 | 21 | try do 22 | download_versions 23 | |> Enum.each(fn {version, builds} -> 24 | Enum.each(builds, fn {os, arch, sha} -> 25 | # A dir for the output of the current build 26 | current_build_output_dir = Path.join(tmp_octo_fetch_dir, sha) 27 | File.mkdir_p!(current_build_output_dir) 28 | 29 | assert {:ok, _successful_files, _failed_files} = 30 | downloader_module.download(current_build_output_dir, 31 | override_version: version, 32 | override_operating_system: os, 33 | override_architecture: arch 34 | ) 35 | 36 | File.rm_rf!(current_build_output_dir) 37 | end) 38 | end) 39 | after 40 | # Delete all of the file downloads 41 | if File.exists?(tmp_octo_fetch_dir) do 42 | File.rm_rf!(tmp_octo_fetch_dir) 43 | end 44 | end 45 | end 46 | 47 | @doc """ 48 | Test that the specified version can be downloaded 49 | """ 50 | def test_version_for_current_platform(downloader_module, version) do 51 | # Create a place to store the file downloads 52 | tmp_octo_fetch_dir = Path.join(System.tmp_dir!(), "/octo_fetch_test_downloads") 53 | File.mkdir_p!(tmp_octo_fetch_dir) 54 | 55 | try do 56 | # A dir for the output of the current build 57 | current_build_output_dir = Path.join(tmp_octo_fetch_dir, version) 58 | File.mkdir_p!(current_build_output_dir) 59 | 60 | assert {:ok, _successful_files, _failed_files} = 61 | downloader_module.download(current_build_output_dir, override_version: version) 62 | 63 | File.rm_rf!(current_build_output_dir) 64 | after 65 | # Delete all of the file downloads 66 | if File.exists?(tmp_octo_fetch_dir) do 67 | File.rm_rf!(tmp_octo_fetch_dir) 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /test/octo_fetch_test.exs: -------------------------------------------------------------------------------- 1 | defmodule OctoFetchTest do 2 | use ExUnit.Case 3 | 4 | import ExUnit.CaptureLog 5 | 6 | defmodule Litestream.Fetcher do 7 | use OctoFetch, 8 | latest_version: "0.3.9", 9 | github_repo: "benbjohnson/litestream", 10 | download_versions: %{ 11 | "0.3.9" => [ 12 | {:linux, :amd64, "806e1cca4a2a105a36f219a4c212a220569d50a8f13f45f38ebe49e6699ab99f"}, 13 | {:darwin, :amd64, "74599a34dc440c19544f533be2ef14cd4378ec1969b9b4fcfd24158946541869"}, 14 | {:darwin, :arm64, "74599a34dc440c19544f533be2ef14cd4378ec1969b9b4fcfd24158946541869"} 15 | ], 16 | "0.3.8" => [ 17 | {:linux, :amd64, "530723d95a51ee180e29b8eba9fee8ddafc80a01cab7965290fb6d6fc31381b3"} 18 | ] 19 | } 20 | 21 | @impl true 22 | def download_name(version, :darwin, _arch), do: "litestream-v#{version}-darwin-amd64.zip" 23 | def download_name(version, :linux, arch), do: "litestream-v#{version}-linux-#{arch}.tar.gz" 24 | end 25 | 26 | test "Should download all of the specified versions" do 27 | OctoFetch.Test.test_all_supported_downloads(Litestream.Fetcher) 28 | end 29 | 30 | test "Should download the specified version on the current platform" do 31 | OctoFetch.Test.test_version_for_current_platform(Litestream.Fetcher, "0.3.9") 32 | end 33 | 34 | test "Should return an error if an invalid version is provided" do 35 | capture_log(fn -> 36 | assert {:error, "invalid is not a supported version"} = 37 | Litestream.Fetcher.download(".", override_version: "invalid") 38 | end) =~ "invalid is not a supported version" 39 | end 40 | 41 | test "Should return an error if an invalid output directory is provided" do 42 | capture_log(fn -> 43 | assert {:error, "Output directory ./this/dir/does/not/exist does not exist"} = 44 | Litestream.Fetcher.download("./this/dir/does/not/exist") 45 | end) =~ "Output directory ./this/dir/does/not/exist does not exist" 46 | end 47 | 48 | test "Should return an error if an invalid architecture is provided" do 49 | capture_log(fn -> 50 | assert {:error, "Your platform is not supported for the provided version" <> _} = 51 | Litestream.Fetcher.download(".", override_architecture: :bad_arch) 52 | end) =~ "Your platform is not supported for the provided version" 53 | end 54 | 55 | test "Should return an error if an invalid OS is provided" do 56 | capture_log(fn -> 57 | assert {:error, "Your platform is not supported for the provided version" <> _} = 58 | Litestream.Fetcher.download(".", override_operating_system: :bad_os) 59 | end) =~ "Your platform is not supported for the provided version" 60 | end 61 | 62 | @tag :tmp_dir 63 | test "Should return an :ok tuple with the archive files", %{tmp_dir: tmp_dir} do 64 | expected_output = Path.join(tmp_dir, "litestream") 65 | assert {:ok, [^expected_output], []} = Litestream.Fetcher.download(tmp_dir) 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/octo_fetch/downloader.ex: -------------------------------------------------------------------------------- 1 | defmodule OctoFetch.Downloader do 2 | @moduledoc """ 3 | This module defines the callbacks that a GitHub downloader needs 4 | to implement in order to fetch artifacts from GitHub. `base_url/2` and 5 | `default_version/0` are automatically implemented for you when you use 6 | the `OctoFetch` module, but you always have the option to 7 | override their default implementations. 8 | """ 9 | 10 | @type os() :: :linux | :darwin | :freebsd | :windows 11 | @type arch() :: :arm64 | :amd64 12 | @type download_result() :: {:ok, successful_files :: list(), failed_files :: list()} | {:error, String.t()} | :skip 13 | 14 | @doc """ 15 | This callback generates the base URL for the artifact based on the provided GitHub repo 16 | and the requested version. The default implementation from `OctoFetch` is: 17 | 18 | ```elixir 19 | def base_url(github_repo, version) do 20 | "https://github.com/\#{github_repo}/releases/download/v\#{version}/" 21 | end 22 | ``` 23 | """ 24 | @callback base_url(github_repo :: String.t(), version :: String.t()) :: String.t() 25 | 26 | @doc """ 27 | This callback returns the default version that sould be downloaded if the 28 | user does not override the version. It will default to the value of `:latest_version` 29 | as provided to the `OctoFetch` `__using__/1` macro. 30 | """ 31 | @callback default_version :: String.t() 32 | 33 | @doc """ 34 | This function must be implemented by your downloader module and is used to 35 | dynamically generate the name of the download artifact based on the user's 36 | running environment. For example, for Litestream you would do something like 37 | so to ensure that users download the proper artifact: 38 | 39 | ```elixir 40 | def download_name(version, :darwin, arch), do: "litestream-v\#{version}-darwin-\#{arch}.zip" 41 | def download_name(version, :linux, arch), do: "litestream-v\#{version}-linux-\#{arch}.tar.gz" 42 | ``` 43 | """ 44 | @callback download_name(version :: String.t(), operation_system :: os(), architecture :: arch()) :: 45 | String.t() 46 | 47 | @doc """ 48 | This callback is invoked whenever a file is written to the filesystem as 49 | a result from the download. This callback may be invoked several times 50 | if the download was an archive file and contained multiple files. 51 | """ 52 | @callback post_write_hook(file :: String.t()) :: :ok 53 | 54 | @doc """ 55 | This callback is invoked prior to a file being downloaded. This gives you 56 | the opportunity to skip the download if you so chose by returning `:skip`. Otherwise, 57 | return `:cont` to continue with the download. 58 | """ 59 | @callback pre_download_hook(file :: String.t(), output_dir :: String.t()) :: :cont | :skip 60 | 61 | @doc """ 62 | This callback acts as a pass through to the `OctoFetch` module for the 63 | downloader implementation. See `OctoFetch.download/3` for supported `opts`. 64 | """ 65 | @callback download(output_dir :: String.t(), opts :: Keyword.t()) :: download_result() 66 | end 67 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule OctoFetch.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :octo_fetch, 7 | version: "0.4.0", 8 | elixir: "~> 1.13", 9 | name: "OctoFetch", 10 | source_url: "https://github.com/akoutmos/octo_fetch", 11 | homepage_url: "https://hex.pm/packages/octo_fetch", 12 | description: "Download, verify, and extract GitHub release artifacts effortlessly right from Elixir", 13 | elixirc_paths: elixirc_paths(Mix.env()), 14 | start_permanent: Mix.env() == :prod, 15 | test_coverage: [tool: ExCoveralls], 16 | preferred_cli_env: [ 17 | coveralls: :test, 18 | "coveralls.detail": :test, 19 | "coveralls.post": :test, 20 | "coveralls.html": :test, 21 | "coveralls.github": :test 22 | ], 23 | dialyzer: [ 24 | plt_file: {:no_warn, "priv/plts/dialyzer.plt"} 25 | ], 26 | package: package(), 27 | deps: deps(), 28 | docs: docs(), 29 | aliases: aliases() 30 | ] 31 | end 32 | 33 | # Run "mix help compile.app" to learn about applications. 34 | def application do 35 | [ 36 | extra_applications: [:logger, :inets, :public_key] 37 | ] 38 | end 39 | 40 | defp elixirc_paths(:test), do: ["lib", "test/support"] 41 | defp elixirc_paths(_), do: ["lib"] 42 | 43 | # Run "mix help deps" to learn about dependencies. 44 | defp deps do 45 | [ 46 | # Production dependencies 47 | {:castore, "~> 0.1 or ~> 1.0"}, 48 | {:ssl_verify_fun, "~> 1.1"}, 49 | 50 | # Development dependencies 51 | {:ex_doc, "~> 0.30.9", only: :dev}, 52 | {:excoveralls, "~> 0.18.0", only: :test, runtime: false}, 53 | {:credo, "~> 1.7.1", only: :dev}, 54 | {:dialyxir, "~> 1.4.2", only: :dev, runtime: false}, 55 | {:git_hooks, "~> 0.7.3", only: [:test, :dev], runtime: false} 56 | ] 57 | end 58 | 59 | defp docs do 60 | [ 61 | main: "readme", 62 | source_ref: "master", 63 | logo: "guides/images/logo.svg", 64 | extras: [ 65 | "README.md" 66 | ] 67 | ] 68 | end 69 | 70 | defp package do 71 | [ 72 | name: "octo_fetch", 73 | files: ~w(lib mix.exs README.md LICENSE CHANGELOG.md), 74 | licenses: ["MIT"], 75 | maintainers: ["Alex Koutmos"], 76 | links: %{ 77 | "GitHub" => "https://github.com/akoutmos/octo_fetch", 78 | "Sponsor" => "https://github.com/sponsors/akoutmos" 79 | } 80 | ] 81 | end 82 | 83 | defp aliases do 84 | [ 85 | docs: ["docs", ©_files/1] 86 | ] 87 | end 88 | 89 | defp copy_files(_) do 90 | # Set up directory structure 91 | File.mkdir_p!("./doc/guides/images") 92 | 93 | # Copy over image files 94 | "./guides/images/" 95 | |> File.ls!() 96 | |> Enum.each(fn image_file -> 97 | File.cp!("./guides/images/#{image_file}", "./doc/guides/images/#{image_file}") 98 | end) 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /guides/images/logo.svg: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
7 | Download, verify, and extract GitHub release artifacts effortlessly right from Elixir 8 |
9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
63 |
64 |
65 | ### Silver Sponsors
66 |
67 |
68 |
69 |
70 |
71 | ### Bronze Sponsors
72 |
73 |
74 |
75 |
76 |
77 | ## Setting Up OctoFetch
78 |
79 | If you want to create a downloader utility for a particular GitHub repository, you can use this library
80 | to take care of all of the boilerplate, validation, and archive extraction. For example, if you want to create
81 | a downloader for Litestream, you can do the following:
82 |
83 | ```elixir
84 | defmodule LiteStream.Downloader do
85 | use OctoFetch,
86 | latest_version: "0.3.9",
87 | github_repo: "benbjohnson/litestream",
88 | download_versions: %{
89 | "0.3.9" => [
90 | {:darwin, :amd64, "74599a34dc440c19544f533be2ef14cd4378ec1969b9b4fcfd24158946541869"},
91 | {:linux, :amd64, "806e1cca4a2a105a36f219a4c212a220569d50a8f13f45f38ebe49e6699ab99f"},
92 | {:linux, :arm64, "61acea9d960633f6df514972688c47fa26979fbdb5b4e81ebc42f4904394c5c5"}
93 | ],
94 | "0.3.8" => [
95 | {:darwin, :amd64, "d359a4edd1cb98f59a1a7c787bbd0ed30c6cc3126b02deb05a0ca501ff94a46a"},
96 | {:linux, :amd64, "530723d95a51ee180e29b8eba9fee8ddafc80a01cab7965290fb6d6fc31381b3"},
97 | {:linux, :arm64, "1d6fb542c65b7b8bf91c8859d99f2f48b0b3251cc201341281f8f2c686dd81e2"}
98 | ]
99 | }
100 |
101 | # You must implement this function to generate the names of the downloads based on the
102 | # user's current running environment
103 | @impl true
104 | def download_name(version, :darwin, arch), do: "litestream-v\#{version}-darwin-\#{arch}.zip"
105 | def download_name(version, :linux, arch), do: "litestream-v\#{version}-linux-\#{arch}.tar.gz"
106 | end
107 | ```
108 |
109 | You would then be able to download the release artifact by doing the following:
110 |
111 | ```elixir
112 | iex (1) > Litestream.Downloader.download(".")
113 | {:ok, ["./litestream"], []}
114 | ```
115 |
116 | ## Attribution
117 |
118 | It wouldn't be right to not include somewhere in this project a "thank you" to the various projects and people that
119 | helped make this possible:
120 |
121 | - The logo for the project is an edited version of an SVG image from the [unDraw project](https://undraw.co/)
122 | - The work done in [Phoenix Tailwind](https://github.com/phoenixframework/tailwind) served as a baseline for how to
123 | structure this library.
124 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{
2 | "blankable": {:hex, :blankable, "1.0.0", "89ab564a63c55af117e115144e3b3b57eb53ad43ba0f15553357eb283e0ed425", [:mix], [], "hexpm", "7cf11aac0e44f4eedbee0c15c1d37d94c090cb72a8d9fddf9f7aec30f9278899"},
3 | "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
4 | "castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"},
5 | "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
6 | "credo": {:hex, :credo, "1.7.1", "6e26bbcc9e22eefbff7e43188e69924e78818e2fe6282487d0703652bc20fd62", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e9871c6095a4c0381c89b6aa98bc6260a8ba6addccf7f6a53da8849c748a58a2"},
7 | "dialyxir": {:hex, :dialyxir, "1.4.2", "764a6e8e7a354f0ba95d58418178d486065ead1f69ad89782817c296d0d746a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "516603d8067b2fd585319e4b13d3674ad4f314a5902ba8130cd97dc902ce6bbd"},
8 | "earmark_parser": {:hex, :earmark_parser, "1.4.38", "b42252eddf63bda05554ba8be93a1262dc0920c721f1aaf989f5de0f73a2e367", [:mix], [], "hexpm", "2cd0907795aaef0c7e8442e376633c5b3bd6edc8dbbdc539b22f095501c1cdb6"},
9 | "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
10 | "ex_doc": {:hex, :ex_doc, "0.30.9", "d691453495c47434c0f2052b08dd91cc32bc4e1a218f86884563448ee2502dd2", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "d7aaaf21e95dc5cddabf89063327e96867d00013963eadf2c6ad135506a8bc10"},
11 | "excoveralls": {:hex, :excoveralls, "0.18.0", "b92497e69465dc51bc37a6422226ee690ab437e4c06877e836f1c18daeb35da9", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1109bb911f3cb583401760be49c02cbbd16aed66ea9509fc5479335d284da60b"},
12 | "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
13 | "git_hooks": {:hex, :git_hooks, "0.7.3", "09489e94d88dfc767662e22aff2b6208bd7cf555a19dd0e1477cca4683ce0701", [:mix], [{:blankable, "~> 1.0.0", [hex: :blankable, repo: "hexpm", optional: false]}, {:recase, "~> 0.7.0", [hex: :recase, repo: "hexpm", optional: false]}], "hexpm", "d6ddedeb4d3a8602bc3f84e087a38f6150a86d9e790628ed8bc70e6d90681659"},
14 | "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
15 | "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
16 | "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
17 | "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"},
18 | "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
19 | "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"},
20 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
21 | "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
22 | "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
23 | "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
24 | "recase": {:hex, :recase, "0.7.0", "3f2f719f0886c7a3b7fe469058ec539cb7bbe0023604ae3bce920e186305e5ae", [:mix], [], "hexpm", "36f5756a9f552f4a94b54a695870e32f4e72d5fad9c25e61bc4a3151c08a4e0c"},
25 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
26 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
27 | }
28 |
--------------------------------------------------------------------------------
/.credo.exs:
--------------------------------------------------------------------------------
1 | # This file contains the configuration for Credo and you are probably reading
2 | # this after creating it with `mix credo.gen.config`.
3 | #
4 | # If you find anything wrong or unclear in this file, please report an
5 | # issue on GitHub: https://github.com/rrrene/credo/issues
6 | #
7 | %{
8 | #
9 | # You can have as many configs as you like in the `configs:` field.
10 | configs: [
11 | %{
12 | #
13 | # Run any config using `mix credo -C