move mix tasks

This commit is contained in:
Mayel de Borniol 2022-11-29 17:54:48 +13:00
parent 5ff4b36886
commit a0c263f7cb
15 changed files with 1 additions and 1364 deletions

1
lib/mix Symbolic link
View file

@ -0,0 +1 @@
../extensions/bonfire/lib/mix

View file

@ -1,110 +0,0 @@
# Copyright (c) 2020 James Laver, mess Contributors
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
if not Code.ensure_loaded?(Mess) do
defmodule Mess do
@sources [path: "deps.path", git: "deps.git", hex: "deps.hex"]
@newline ~r/(?:\r\n|[\r\n])/
@parser ~r/^(?<indent>\s*)((?<package>[a-z_][a-z0-9_]+)\s*=\s*"(?<value>[^"]+)")?(?<post>.*)/
@git_branch ~r/(?<repo>[^#]+)(#(?<branch>.+))?/
def umbrella_path(opts \\ []),
do: opts[:umbrella_path] || if(Mix.env() != :prod, do: "extensions/", else: nil)
def deps(sources \\ @sources, extra_deps, opts \\ []),
do: Enum.flat_map(sources, fn {k, v} -> read(v, k) end) |> deps_packages(extra_deps, opts)
defp deps_packages(packages, extra_deps, opts),
do: Enum.flat_map(packages, &dep_spec(&1, opts)) |> deps_uniq(extra_deps, opts)
defp deps_uniq(packages, extra_deps, opts),
do: Enum.uniq_by(packages ++ extra_deps, &elem(&1, 0)) |> maybe_filter_umbrella(opts)
defp maybe_filter_umbrella(deps, opts) do
if opts[:umbrella_root?] do
Enum.reject(deps, fn dep ->
dep_opts = elem(dep, 1)
is_list(dep_opts) and dep_opts[:from_umbrella]
end)
# |> IO.inspect(label: "umbrella_root")
else
if umbrella_path(opts) do
umbrella_deps = read_umbrella("../../config/deps.path", opts)
deps
|> Enum.map(fn dep ->
name = elem(dep, 0)
case umbrella_deps[name] do
nil ->
dep
dep_opts ->
if dep_opts[:from_umbrella] do
{name, in_umbrella: true, override: true}
else
{name, dep_opts |> Keyword.put(:path, "../../#{dep_opts[:path]}")}
end
end
end)
# |> IO.inspect(label: "in_umbrella")
else
deps
end
end
end
defp read_umbrella(path, opts) when is_binary(path) do
if File.exists?(path) do
read(path, :path)
|> Enum.flat_map(&dep_spec(&1, opts))
else
[]
end
end
defp read(path, kind) when is_binary(path), do: have_read(File.read(path), kind)
defp have_read({:error, :enoent}, _kind), do: []
defp have_read({:ok, file}, kind),
do: Enum.map(String.split(file, @newline), &read_line(&1, kind))
defp read_line(line, kind),
do: Map.put(Regex.named_captures(@parser, line), :kind, kind)
defp dep_spec(%{"package" => ""}, _opts), do: []
defp dep_spec(%{"package" => p, "value" => v, :kind => :hex}, _opts),
do: pkg(p, v, override: true)
defp dep_spec(%{"package" => p, "value" => v, :kind => :path}, opts) do
umbrella_path = umbrella_path(opts)
if umbrella_path && String.starts_with?(v, umbrella_path) do
pkg(p, from_umbrella: true, override: true, path: v)
else
pkg(p, path: v, override: true)
end
end
defp dep_spec(%{"package" => p, "value" => v, :kind => :git}, _opts), do: git(v, p)
defp git(line, p) when is_binary(line),
do: git(Regex.named_captures(@git_branch, line), p)
defp git(%{"branch" => "", "repo" => r}, p),
do: pkg(p, git: r, override: true)
defp git(%{"branch" => b, "repo" => r}, p),
do: pkg(p, git: r, branch: b, override: true)
defp pkg(name, opts), do: [{String.to_atom(name), opts}]
defp pkg(name, version, opts), do: [{String.to_atom(name), version, opts}]
end
end

View file

@ -1,299 +0,0 @@
if not Code.ensure_loaded?(Bonfire.Mixer) do
defmodule Bonfire.Mixer do
def deps(config, deps_subtype)
def deps(config, :bonfire) do
prefixes = multirepo_prefixes(config)
Enum.filter(config[:deps] || config, &in_multirepo?(&1, prefixes))
end
def deps(config, :update = deps_subtype) do
prefixes = multirepo_prefixes(config)
Enum.filter(
config[:deps] || config,
&(include_dep?(deps_subtype, &1, config[:deps_prefixes][deps_subtype]) ||
in_multirepo?(&1, prefixes))
)
# |> IO.inspect(limit: :infinity)
end
def deps(config, deps_subtype) when is_atom(deps_subtype),
do:
Enum.filter(
config[:deps] || config,
&include_dep?(deps_subtype, &1, config[:deps_prefixes][deps_subtype])
)
def deps_for(type, deps \\ deps()) do
deps(deps, type)
|> Enum.map(&dep_name/1)
end
def deps do
if function_exported?(Mix.Project, :config, 0),
do: Mix.Project.config()[:deps],
else: Bonfire.Application.deps()
end
def mix_config do
if function_exported?(Mix.Project, :config, 0),
do: Mix.Project.config(),
else: Bonfire.Application.config()
end
def multirepo_prefixes(config \\ mix_config()),
do:
List.wrap(config[:deps_prefixes] || mix_config()[:deps_prefixes])
|> Enum.flat_map(fn {_, list} -> list || [] end)
|> Enum.uniq()
def in_multirepo?(dep, deps_prefixes \\ multirepo_prefixes()),
do: include_dep?(:bonfire, dep, deps_prefixes)
def deps_recompile(deps \\ deps_for(:bonfire)),
do: Mix.Task.run("bonfire.dep.compile", ["--force"] ++ List.wrap(deps))
# def flavour_path(path) when is_binary(path), do: path
def flavour_path(config),
do: System.get_env("FLAVOUR_PATH", "flavours/" <> flavour(config))
def flavour(config \\ mix_config())
def flavour(default_flavour) when is_binary(default_flavour),
do: System.get_env("FLAVOUR") || default_flavour
def flavour(config), do: System.get_env("FLAVOUR") || config[:default_flavour]
def config_path(config_or_flavour, filename),
do: Path.expand(Path.join([flavour_path(config_or_flavour), "config", filename]))
def forks_path(), do: System.get_env("FORKS_PATH", "extensions/")
def mess_sources(config_or_flavour) do
do_mess_sources(System.get_env("WITH_FORKS", "1"))
|> Enum.map(fn {k, v} -> {k, config_path(config_or_flavour, v)} end)
end
defp do_mess_sources("0"), do: [git: "deps.git", hex: "deps.hex"]
defp do_mess_sources(_),
do: [path: "deps.path", git: "deps.git", hex: "deps.hex"]
def deps_to_clean(deps, type) do
deps(deps, type)
|> deps_names()
end
def deps_to_update(config) do
deps(config, :update)
|> deps_names()
|> IO.inspect(
label:
"Running Bonfire #{version(config)} with configuration from #{flavour_path(config)} in #{Mix.env()} environment. You can run `just mix bonfire.deps.update` to update these extensions and dependencies"
)
end
# Specifies which paths to include in docs
def beam_paths(deps, type \\ :all) do
build = Mix.Project.build_path()
([:bonfire] ++ deps(deps, type))
|> Enum.map(&beam_path(&1, build))
end
defp beam_path(app, build),
do: Path.join([build, "lib", dep_name(app), "ebin"])
def readme_paths(config),
do:
List.wrap(config[:guides]) ++
Enum.map(Path.wildcard("flavours/*/README.md"), &flavour_readme/1) ++
Enum.map(Path.wildcard("docs/DEPENDENCIES/*.md"), &flavour_deps_doc/1) ++
Enum.flat_map(deps(config, :docs), &readme_path/1)
defp readme_path(dep) when not is_nil(dep),
do: dep_paths(dep, "README.md") |> List.first() |> readme_path(dep)
defp readme_path(path, dep) when not is_nil(path),
do: [{path |> String.to_atom(), [filename: "extension-" <> dep_name(dep)]}]
defp readme_path(_, _), do: []
def flavour_readme(path),
do: {path |> String.to_atom(), [filename: path |> String.split("/") |> Enum.at(1)]}
def flavour_deps_doc(path),
do:
{path |> String.to_atom(),
[
title:
path
|> String.split("/")
|> Enum.at(2)
|> String.slice(0..-4)
|> String.capitalize(),
filename:
path
|> String.split("/")
|> Enum.at(2)
|> String.slice(0..-4)
|> then(&"deps-#{&1}")
]}
# [plug: "https://myserver/plug/"]
def doc_deps(config), do: deps(config, :docs) |> Enum.map(&doc_dep/1)
defp doc_dep(dep), do: {elem(dep, 0), "./"}
def source_url_pattern("deps/" <> _ = path, line),
do: bonfire_ext_pattern(path, line)
def source_url_pattern("extensions/" <> _ = path, line),
do: bonfire_ext_pattern(path, line)
def source_url_pattern("forks/" <> _ = path, line),
do: bonfire_ext_pattern(path, line)
def source_url_pattern(path, line), do: bonfire_app_pattern(path, line)
def bonfire_ext_pattern(path, line),
do:
bonfire_ext_pattern(
path |> String.split("/") |> Enum.at(1),
path |> String.split("/") |> Enum.slice(2..1000) |> Enum.join("/"),
line
)
def bonfire_ext_pattern(dep, path, line),
do:
bonfire_app_pattern(
"https://github.com/bonfire-networks/#{dep}/blob/main/%{path}#L%{line}",
path,
line
)
def bonfire_app_pattern(path, line),
do:
bonfire_app_pattern(
"https://github.com/bonfire-networks/bonfire-app/blob/main/%{path}#L%{line}",
path,
line
)
def bonfire_app_pattern(pattern, path, line),
do:
pattern
|> String.replace("%{path}", "#{path}")
|> String.replace("%{line}", "#{line}")
# Specifies which paths to include when running tests
def test_paths(config),
do: ["test" | Enum.flat_map(deps(config, :test), &dep_paths(&1, "test"))]
# Specifies which paths to compile per environment
def elixirc_paths(config, :test),
do: [
"lib",
"test/support"
| Enum.flat_map(deps(config, :test), &dep_paths(&1, "test/support"))
]
def elixirc_paths(_, env), do: ["lib"] ++ catalogues(env)
def include_dep?(type, dep, config_or_prefixes)
def include_dep?(:update, dep, _config_or_prefixes) when is_tuple(dep),
do: unpinned_git_dep?(dep)
# defp include_dep?(:docs = type, dep, deps_prefixes), do: String.starts_with?(dep_name(dep), deps_prefixes || @config[:deps_prefixes][type]) || git_dep?(dep)
def include_dep?(type, dep, config_or_prefixes) do
# IO.inspect(config_or_prefixes)
String.starts_with?(
dep_name(dep),
config_or_prefixes[:deps_prefixes][type] || config_or_prefixes[type] || config_or_prefixes
)
end
# defp git_dep?(dep) do
# spec = elem(dep, 1)
# is_list(spec) && spec[:git]
# end
def unpinned_git_dep?(dep) do
spec = elem(dep, 1)
is_list(spec) && spec[:git] && !spec[:commit]
end
def dep_name(dep) when is_tuple(dep), do: elem(dep, 0) |> dep_name()
def dep_name(dep) when is_atom(dep), do: Atom.to_string(dep)
def dep_name(dep) when is_binary(dep), do: dep
def deps_names(deps) do
deps
|> Enum.map(&dep_name/1)
|> Enum.join(" ")
end
def dep_path(dep) when is_binary(dep) do
path_if_exists(forks_path() <> dep) ||
path_if_exists(
(Mix.Project.deps_path() <> "/" <> dep)
|> Path.expand(File.cwd!())
) ||
"."
end
def dep_path(dep) do
spec = elem(dep, 1)
path =
if is_list(spec) && spec[:path],
do: spec[:path],
else:
(Mix.Project.deps_path() <> "/" <> dep_name(dep))
|> Path.relative_to_cwd()
path_if_exists(path)
end
defp path_if_exists(path), do: if(File.exists?(path), do: path)
def dep_paths(dep, extra) when is_list(extra),
do: Enum.flat_map(extra, &dep_paths(dep, &1))
def dep_paths(dep, extra) when is_binary(extra) do
dep_path = dep_path(dep)
if dep_path do
path = Path.join(dep_path, extra) |> path_if_exists()
if path, do: [path], else: []
else
[]
end
end
def version(config) do
config[:version]
|> String.split("-", parts: 2)
|> List.insert_at(1, flavour(config))
|> Enum.join("-")
end
# def compilers(:dev) do
# [:unused] ++ compilers(nil)
# end
def compilers(_) do
Mix.compilers()
end
def catalogues(_env) do
[
"deps/surface/priv/catalogue",
dep_path("bonfire_ui_social") <> "/priv/catalogue"
]
end
end
end

View file

@ -1,103 +0,0 @@
## About half of this code is taken from hex, therefore this whole
## file is considered under the same license terms as hex.
defmodule Mix.Tasks.Bonfire.Account.New do
use Mix.Task
@shortdoc "Creates a new account in the database"
@moduledoc """
Creates an account in the database, automatically activated
## Usage
```
mix bonfire.account.new [email@address]
```
You will be prompted for a password and an email if it was not provided.
"""
alias Bonfire.Me.Fake
@spec run(OptionParser.argv()) :: :ok
def run(args) do
options = options(args, %{})
Mix.Task.run("app.start")
email = get("Enter an email address: ", :email, options, true)
password = password("Enter a password:")
IO.inspect(password: password)
Fake.fake_account!(%{
credential: %{password: password},
email: %{email_address: email}
})
end
defp options([], opts), do: opts
defp options([email], opts), do: Map.put(opts, :email, email)
defp get(prompt, key, opts, must?) do
case opts[key] do
nil ->
case IO.gets(prompt) do
:eof ->
raise RuntimeError, message: "EOF"
data when is_binary(data) ->
get(prompt, key, Map.put(opts, key, data), must?)
data when is_list(data) ->
get(prompt, key, Map.put(opts, key, to_string(data)), must?)
end
data ->
data = String.trim(data)
if data == "" do
if must?,
do: get(prompt, key, Map.delete(opts, key), must?),
else: nil
else
data
end
end
end
# Extracted from hex via https://dev.to/tizpuppi/password-input-in-elixir-31oo
defp password(prompt) do
pid = spawn_link(fn -> loop(prompt) end)
ref = make_ref()
password(prompt, pid, ref)
end
defp password(prompt, pid, ref) do
value = String.trim(IO.gets(prompt))
if String.length(value) < 10 do
IO.puts(
:standard_error,
"Password too short, must be at least 10 characters long"
)
password(prompt, pid, ref)
else
send(pid, {:done, self(), ref})
receive do
{:done, ^pid, ^ref} -> value
end
end
end
defp loop(prompt) do
receive do
{:done, parent, ref} ->
send(parent, {:done, self(), ref})
IO.write(:standard_error, "\e[2K\r")
after
1 ->
IO.write(:standard_error, "\e[2K\r#{prompt}")
loop(prompt)
end
end
end

View file

@ -1,35 +0,0 @@
defmodule Mix.Tasks.Import2alias do
use Mix.Task
@impl true
def run(args) do
unless Version.match?(System.version(), ">= 1.10.0-rc") do
Mix.raise("Elixir v1.10+ is required!")
end
case args do
[module, alias] ->
run(Module.concat([module]), Module.concat([alias]))
_ ->
Mix.raise("Usage: elixir -r lib_import2alias.ex -S mix import2alias MODULE ALIAS")
end
end
defp run(module, alias) do
{:ok, _} = Import2Alias.Server.start_link(module)
Code.compiler_options(parser_options: [columns: true])
args = ["--force", "--tracer", "Import2Alias.CallerTracer"]
# Mix.Task.rerun("compile.elixir", args)
deps = Bonfire.Mixer.deps_for(:bonfire)
# |> IO.inspect()
Mix.Tasks.Bonfire.Deps.Compile.try_compile(deps, args)
entries = Import2Alias.Server.entries()
Import2Alias.import2alias(alias, entries)
end
end

View file

@ -1,279 +0,0 @@
defmodule Mix.Tasks.Bonfire.Deps.Compile do
use Mix.Task
import Untangle
@shortdoc "Compiles dependencies"
@moduledoc """
(re)compiles dependencies.
This is a modified version of Elixir's `Mix.Tasks.Deps.Compile` which was needed to compile dependencies and extract localisable strings in `Mix.Tasks.Bonfire.Localise.Extract`
By default, compile all dependencies. A list of dependencies
can be given compile multiple dependencies in order.
This task attempts to detect if the project contains one of
the following files and act accordingly:
* `mix.exs` - invokes `mix compile`
* otherwise skip
If a list of dependencies is given, Mix will attempt to compile
them as is. For example, if project `a` depends on `b`, calling
`mix deps.compile a` will compile `a` even if `b` is out of
date. This is to allow parts of the dependency tree to be
recompiled without propagating those changes upstream. To ensure
`b` is included in the compilation step, pass `--include-children`.
"""
import Mix.Dep, only: [available?: 1, mix?: 1]
@switches [include_children: :boolean, force: :boolean]
def force_compile(dep_or_deps, compile_args \\ []) do
# mark deps to be recompiled (run this task)
Mix.Tasks.Bonfire.Deps.Compile.run(["--force"] ++ List.wrap(dep_or_deps))
# If "compile" was never called, the reenabling is a no-op and
# "compile.elixir" is a no-op as well (because it wasn't re-enabled after
# running "compile"). If "compile" was already called, then running
# "compile" is a no-op and running "compile.elixir" will work because we
# manually re-enabled it.
Mix.Task.reenable("compile.elixir")
Mix.Task.run("compile", compile_args)
Mix.Task.run("compile.elixir", compile_args)
end
def try_compile(dep_or_deps, compile_args \\ []) do
# mark deps to be recompiled (run this task)
Mix.Tasks.Bonfire.Deps.Compile.run(["--force"] ++ List.wrap(dep_or_deps))
Mix.Task.rerun("compile.elixir", compile_args)
end
@spec run(OptionParser.argv()) :: :ok
def run(args) do
unless "--no-archives-check" in args do
Mix.Task.run("archive.check", args)
end
Mix.Project.get!()
case OptionParser.parse(args, switches: @switches) do
{opts, [], _} ->
# Because this command may be invoked explicitly with
# dep.compile, we simply try to compile any available
# dependency.
compile(Enum.filter(loaded_deps(), &available?/1), opts)
{opts, tail, _} ->
compile(loaded_by_name(tail, [env: Mix.env()] ++ opts), opts)
end
end
@doc false
def compile(deps, options \\ []) do
shell = Mix.shell()
config = Mix.Project.deps_config()
Mix.Task.run("deps.precompile")
compiled =
Enum.map(deps, fn %Mix.Dep{app: app, status: status, opts: opts, scm: scm} = dep ->
check_unavailable!(app, status)
compiled? =
cond do
mix?(dep) ->
maybe_clean(dep, options)
do_mix(dep, config)
true ->
shell.error(
"Could not compile #{inspect(app)}, no \"mix.exs\" found " <>
"(pass :compile as an option to customize compilation, set it to \"false\" to do nothing)"
)
false
end
# We should touch fetchable dependencies even if they
# did not compile otherwise they will always be marked
# as stale, even when there is nothing to do.
fetchable? = touch_fetchable(scm, opts[:build])
compiled? and fetchable?
end)
if true in compiled, do: Mix.Task.run("will_recompile"), else: :ok
end
defp maybe_clean(dep, opts) do
# If a dependency was marked as fetched or with an out of date lock
# or missing the app file, we always compile it from scratch.
if Keyword.get(opts, :force, false) or Mix.Dep.compilable?(dep) do
File.rm_rf!(Path.join([Mix.Project.build_path(), "lib", Atom.to_string(dep.app)]))
end
end
defp touch_fetchable(scm, path) do
if scm.fetchable? do
File.mkdir_p!(path)
File.touch!(Path.join(path, ".compile.fetch"))
true
else
false
end
end
defp check_unavailable!(app, {:unavailable, _}) do
Mix.raise(
"Cannot compile dependency #{inspect(app)} because " <>
"it isn't available, run \"mix deps.get\" first"
)
end
defp check_unavailable!(_, _) do
:ok
end
defp do_mix(dep, _config) do
Mix.Dep.in_dependency(dep, fn _ ->
if req = old_elixir_req(Mix.Project.config()) do
Mix.shell().error(
"warning: the dependency #{inspect(dep.app)} requires Elixir #{inspect(req)} " <>
"but you are running on v#{System.version()}"
)
end
Mix.shell().info("Recompiling extension #{inspect(dep.app)}")
try do
# If "compile" was never called, the reenabling is a no-op and
# "compile.elixir" is a no-op as well (because it wasn't re-enabled after
# running "compile"). If "compile" was already called, then running
# "compile" is a no-op and running "compile.elixir" will work because we
# manually re-enabled it.
Mix.Task.reenable("compile.elixir")
Mix.Task.reenable("compile.leex")
Mix.Task.reenable("compile.all")
Mix.Task.reenable("compile")
options = [
# "--force",
"--no-deps-loading",
"--no-apps-loading",
"--no-archives-check",
"--no-elixir-version-check",
"--no-warnings-as-errors"
]
res = Mix.Task.run("compile", options)
# Mix.shell.info(inspect res)
match?({:ok, _}, res)
catch
kind, reason ->
app = dep.app
Mix.shell().error(
"could not compile dependency #{inspect(app)}, \"mix compile\" failed. " <>
"You can recompile this dependency with \"mix deps.compile #{app}\", update it " <>
"with \"mix deps.update #{app}\" or clean it with \"mix deps.clean #{app}\""
)
:erlang.raise(kind, reason, __STACKTRACE__)
end
end)
end
defp old_elixir_req(config) do
req = config[:elixir]
if req && not Version.match?(System.version(), req) do
req
end
end
defp loaded_deps() do
if Keyword.has_key?(Mix.Dep.__info__(:functions), :cached) do
Mix.Dep.cached()
else
Mix.Dep.loaded()
end
end
@doc """
Receives a list of dependency names and returns loaded `Mix.Dep`s.
Logs a message if the dependency could not be found.
## Exceptions
This function raises an exception if any of the dependencies
provided in the project are in the wrong format.
"""
def loaded_by_name(given, all_deps \\ nil, opts) do
all_deps = all_deps || loaded_deps()
# |> debug("all_deps")
# Ensure all apps are atoms
apps =
to_app_names(given)
|> debug("deps to recompile")
deps =
if opts[:include_children] do
get_deps_with_children(all_deps, apps)
else
get_deps(all_deps, apps)
end
Enum.each(apps, fn app ->
unless Enum.any?(all_deps, &(&1.app == app)) do
warn("Unknown dependency #{app} for environment #{Mix.env()}")
end
end)
deps
end
defp to_app_names(given) do
Enum.map(given, fn app ->
if is_binary(app), do: String.to_atom(app), else: app
end)
end
defp get_deps(all_deps, apps) do
Enum.filter(all_deps, &(&1.app in apps))
end
defp get_deps_with_children(all_deps, apps) do
deps = get_children(all_deps, apps)
apps = deps |> Enum.map(& &1.app) |> Enum.uniq()
get_deps(all_deps, apps)
end
defp get_children(_all_deps, []), do: []
defp get_children(all_deps, apps) do
# Current deps
deps = get_deps(all_deps, apps)
# Children apps
apps =
for %{deps: children} <- deps,
%{app: app} <- children,
do: app
# Current deps + children deps
deps ++ get_children(all_deps, apps)
end
def touch_manifests() do
# |> debug("manifests")
Enum.map(Mix.Tasks.Compile.Elixir.manifests(), &make_old_if_exists/1)
end
defp make_old_if_exists(path) do
:file.change_time(path, {{2000, 1, 1}, {0, 0, 0}})
end
end

View file

@ -1,109 +0,0 @@
defmodule Mix.Tasks.Docs.Deps do
use Mix.Task
@shortdoc "Generates docs for your app and its all deps"
@recursive true
@root Bonfire.Common.Config.get(:root_path)
@moduledoc """
`mix docs.deps`
## Command line options
* `--only` - the environment to include dependencies for
* `--target` - the target to include dependencies for
* `--exclude` - exclude dependencies which you do not want to see in docs.
* any arguments supported by `mix docs` will be passed along
"""
@switches [only: :string, target: :string, exclude: :keep]
@impl true
def run(args) do
Mix.Project.get!()
{opts, args, _} = OptionParser.parse(args, switches: @switches)
deps_opts =
for {switch, key} <- [only: :env, target: :target],
value = opts[switch],
do: {key, :"#{value}"}
excluded = Keyword.get_values(opts, :exclude)
deps =
Mix.Dep.load_on_environment(deps_opts)
|> prepare_list(opts)
|> List.flatten()
|> Enum.reject(&(Atom.to_string(dep_name(&1)) in excluded))
|> Enum.uniq_by(&dep_name(&1))
config = Mix.Project.config()
build_path = Mix.Project.build_path()
docs_config = Keyword.get(config, :docs, [])
docs_config =
docs_config
|> Keyword.put(:deps, Enum.map(deps, &{dep_name(&1), "./"}))
|> Keyword.put(
:source_beam,
Enum.map(
deps,
&Path.join([build_path, "lib", Atom.to_string(dep_name(&1)), "ebin"])
)
)
|> Keyword.put(
:extras,
Keyword.get(docs_config, :extras, []) ++
Enum.flat_map(deps, &readme_path/1)
)
Mix.Tasks.Docs.run(args, Keyword.put(config, :docs, docs_config))
end
defp prepare_list(deps, opts) when is_list(deps) do
Enum.flat_map(deps, fn
%Mix.Dep{deps: nested_deps} = dep ->
[dep] ++ prepare_list(nested_deps, opts)
dep ->
[dep]
end)
end
defp readme_path(dep) when not is_nil(dep),
do: dep_paths(dep, "README.md") |> List.first() |> readme_path(dep)
defp readme_path(path, dep) when not is_nil(path),
do: [{String.to_atom(path), [filename: "extension-#{dep_name(dep)}"]}]
defp readme_path(_, _), do: []
defp dep_path(dep) do
path =
if is_list(dep) && dep[:path],
do: dep[:path],
else: (Mix.Project.deps_path() <> "/#{dep_name(dep)}") |> Path.expand(@root)
path_if_exists(path)
end
defp dep_paths(dep, extra) when is_list(extra),
do: Enum.flat_map(extra, &dep_paths(dep, &1))
defp dep_paths(dep, extra) when is_binary(extra) do
dep_path = dep_path(dep)
if dep_path do
path = Path.join(dep_path, extra) |> path_if_exists()
if path, do: [path], else: []
else
[]
end
end
defp dep_name(%Mix.Dep{app: dep}) when is_atom(dep), do: dep
defp dep_name(dep) when is_tuple(dep), do: elem(dep, 0) |> dep_name()
defp dep_name(dep) when is_atom(dep), do: dep
defp dep_name(dep) when is_binary(dep), do: dep
defp path_if_exists(path), do: if(File.exists?(path), do: path)
end

View file

@ -1,99 +0,0 @@
defmodule Mix.Tasks.Bonfire.Localise.Extract do
use Mix.Task
import Untangle
@recursive true
@shortdoc "Extracts translations from source code"
@moduledoc """
Extracts translations by recompiling the Elixir source code.
mix gettext.extract [OPTIONS]
Translations are extracted into POT (Portable Object Template) files (with a
`.pot` extension). The location of these files is determined by the `:otp_app`
and `:priv` options given by Gettext modules when they call `use Gettext`. One
POT file is generated for each translation domain.
It is possible to give the `--merge` option to perform merging
for every Gettext backend updated during merge:
mix gettext.extract --merge
All other options passed to `gettext.extract` are forwarded to the
`gettext.merge` task (`Mix.Tasks.Gettext.Merge`), which is called internally
by this task. For example:
mix gettext.extract --merge --no-fuzzy
"""
@switches [merge: :boolean]
def run(args) do
Application.ensure_all_started(:gettext)
_ = Mix.Project.get!()
mix_config = Mix.Project.config()
{opts, _} = OptionParser.parse!(args, switches: @switches)
gettext_config =
(mix_config[:gettext] || [])
|> debug("gettext config")
exts_to_localise =
Bonfire.Mixer.deps_for(:localise)
|> debug("bonfire extensions to localise")
deps_to_localise =
Bonfire.Mixer.deps_for(:localise_self)
|> debug("other deps to localise")
Mix.Tasks.Bonfire.Deps.Compile.touch_manifests()
# first extract strings from all deps that use the Gettext module in bonfire_common
pot_files = extract(:bonfire_common, gettext_config, exts_to_localise)
# then those that have their own Gettext
pot_files =
Enum.reduce(deps_to_localise, pot_files, fn dep, pot_files ->
pot_files ++ extract(String.to_atom(dep), gettext_config, dep)
end)
# pot_files |> debug("extracted pot_files")
for {path, contents} <- pot_files do
File.mkdir_p!(Path.dirname(path))
File.write!(path, contents)
info("Extracted strings to #{Path.relative_to_cwd(path)}")
end
if opts[:merge] do
run_merge(pot_files, args)
end
:ok
end
defp extract(app, gettext_config, deps_to_localise) do
Gettext.Extractor.enable()
Mix.Tasks.Bonfire.Deps.Compile.force_compile(deps_to_localise)
Gettext.Extractor.pot_files(
app,
gettext_config
)
after
Gettext.Extractor.disable()
end
defp run_merge(pot_files, argv) do
pot_files
|> Enum.map(fn {path, _} -> Path.dirname(path) end)
|> Enum.uniq()
|> Task.async_stream(&Mix.Tasks.Gettext.Merge.run([&1 | argv]),
ordered: false
)
|> Stream.run()
end
end

View file

@ -1,30 +0,0 @@
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where third-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Ignore package tarball (built via "mix hex.build").
release-*.tar
# Temporary files for e.g. tests
/tmp
# The build
release

View file

@ -1,166 +0,0 @@
defmodule Releaser.VersionUtils do
@doc """
Some utilities to get and set version numbers in the `mix.exs` file
and to programmatically transform version numbers.
Maybe the `bump_*` functions should be in the standard library?
This script doesn't support pre-release versions or versions with build information.
"""
@version_line_regex ~r/(\n\s*version:\s+")([^\n]+)(")/
def bump_major(%Version{} = version) do
%{version | major: version.major + 1, minor: 0, patch: 0}
end
def bump_minor(%Version{} = version) do
%{version | minor: version.minor + 1, patch: 0}
end
def bump_patch(%Version{} = version) do
%{version | patch: version.patch + 1}
end
def bump_pre(%Version{} = version, pre_label) do
IO.inspect(old_pre: version.pre)
new_pre =
if is_list(version.pre) and List.first(version.pre) == pre_label do
[pre_label, List.last(version.pre) + 1]
else
[pre_label, 1]
end
%{version | pre: new_pre}
end
def version_to_string(%Version{
major: major,
minor: minor,
patch: patch,
pre: pre
})
when is_list(pre) and length(pre) > 0 do
"#{major}.#{minor}.#{patch}-" <> Enum.join(pre, ".")
end
def version_to_string(%Version{major: major, minor: minor, patch: patch}) do
"#{major}.#{minor}.#{patch}"
end
def get_version(mix_path \\ ".") do
version =
if Code.ensure_loaded?(Mix.Project) do
Mix.Project.config()[:version]
else
contents = File.read!(mix_path <> "/mix.exs")
Regex.run(@version_line_regex, contents) |> Enum.fetch!(2)
end
|> IO.inspect()
Version.parse!(version)
end
def set_version(version, mix_path \\ ".") do
contents = File.read!(mix_path <> "/mix.exs")
version_string = version_to_string(version)
replaced =
Regex.replace(@version_line_regex, contents, fn _, pre, _version, post ->
"#{pre}#{version_string}#{post}"
end)
File.write!(mix_path <> "/mix.exs", replaced)
end
def update_version(%Version{} = version, "major"), do: bump_major(version)
def update_version(%Version{} = version, "minor"), do: bump_minor(version)
def update_version(%Version{} = version, "patch"), do: bump_patch(version)
def update_version(%Version{} = version, "alpha" = pre_label),
do: bump_pre(version, pre_label)
def update_version(%Version{} = version, "beta" = pre_label),
do: bump_pre(version, pre_label)
def update_version(%Version{} = _version, type),
do: raise("Invalid version type: #{type}")
end
defmodule Releaser.Git do
@doc """
This module contains some git-specific functionality
"""
alias Releaser.VersionUtils
def add_commit_and_tag(version) do
version_string = VersionUtils.version_to_string(version)
Mix.Shell.IO.cmd("git add .", [])
Mix.Shell.IO.cmd(~s'git commit -m "Bumped version number"')
Mix.Shell.IO.cmd(~s'git tag -a v#{version_string} -m "Version #{version_string}"')
end
end
defmodule Releaser.Tests do
def run_tests!() do
error_code = Mix.Shell.IO.cmd("mix test", [])
if error_code != 0 do
raise "This version can't be released because tests are failing."
end
:ok
end
end
defmodule Releaser.Publish do
def publish!() do
error_code = Mix.Shell.IO.cmd("mix hex.publish", [])
if error_code != 0 do
raise "Couldn't publish package on hex."
end
:ok
end
end
defmodule Mix.Tasks.Bonfire.Release do
alias Releaser.VersionUtils
# for running as escript
def main(args) do
run(args)
end
# when running as Mix task
def run(args) do
mix_path = if is_list(args) and length(args) > 0, do: List.first(args), else: "."
# TODO make the default configurable
release_type = if is_list(args) and length(args) > 1, do: List.last(args), else: "alpha"
IO.inspect(release_type: release_type)
# Run the tests before generating the release.
# If any test fails, stop.
# Tests.run_tests!()
# Get the current version from the mix.exs file.
old_version = VersionUtils.get_version(mix_path)
IO.inspect(old_version: old_version)
new_version = VersionUtils.update_version(old_version, release_type)
IO.inspect(new_version: VersionUtils.version_to_string(new_version))
# Set a new version on the mix.exs file
VersionUtils.set_version(new_version, mix_path)
# Commit the changes and ad a new 'v*.*.*' tag
# Git.add_commit_and_tag(new_version)
# Try to publish the package on hex.
# If this fails, we don't want to run all the code above,
# so you should run `mix hex.publish" again manually to try to solve the problem
# Publish.publish!()
end
end

View file

@ -1,12 +0,0 @@
defmodule Bonfire.Release do
use Mix.Project
def project do
[
app: :release,
version: "0.1.0-alpha.1",
elixir: "~> 1.11",
escript: [main_module: Mix.Tasks.Bonfire.Release]
]
end
end

View file

@ -1,30 +0,0 @@
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where third-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Ignore package tarball (built via "mix hex.build").
release-*.tar
# Temporary files for e.g. tests
/tmp
# The build
secrets

View file

@ -1,45 +0,0 @@
defmodule Mix.Tasks.Bonfire.Secrets do
@shortdoc "Generates some secrets"
@moduledoc """
Generates secrets and prints to the terminal.
mix secrets [length]
By default, it generates keys 64 characters long.
The minimum value for `length` is 32.
"""
use Mix.Task
# for running as escript
def main(args) do
run(args)
end
@doc false
def run([]), do: run(["64"])
def run([int]),
do: int |> parse!() |> random_string() |> Kernel.<>("\r\n") |> IO.puts()
def run([int, iterate]), do: for(_ <- 1..parse!(iterate), do: run([int]))
def run(args), do: invalid_args!(args)
defp parse!(int) do
case Integer.parse(int) do
{int, ""} -> int
_ -> invalid_args!(int)
end
end
defp random_string(length) when length > 31 do
:crypto.strong_rand_bytes(length)
|> Base.encode64()
|> binary_part(0, length)
end
defp random_string(_),
do: raise("Secrets should be at least 32 characters long")
defp invalid_args!(args) do
raise "Expected a length as integer or no argument at all, got #{inspect(args)}"
end
end

View file

@ -1,12 +0,0 @@
defmodule Bonfire.Secrets do
use Mix.Project
def project do
[
app: :secrets,
version: "0.1.0-alpha.1",
elixir: "~> 1.11",
escript: [main_module: Mix.Tasks.Bonfire.Secrets]
]
end
end

View file

@ -1,35 +0,0 @@
## About half of this code is taken from hex, therefore this whole
## file is considered under the same license terms as hex.
defmodule Mix.Tasks.Bonfire.User.Admin.Promote do
use Mix.Task
@shortdoc "Promotes a user to an administrator"
@moduledoc """
Creates an account in the database, automatically activated
## Usage
```
mix bonfire.user.admin.promote username
```
"""
alias Bonfire.Me.Users
@spec run(OptionParser.argv()) :: :ok
def run(args) do
options = options(args, %{})
Mix.Task.run("app.start")
case Users.by_username!(options.username) do
nil ->
raise RuntimeError, message: "User not found"
user ->
Users.make_admin(user)
end
end
defp options([username], opts), do: Map.put(opts, :username, username)
end