diff --git a/README.md b/README.md index 19357ae..fa123ed 100644 --- a/README.md +++ b/README.md @@ -30,12 +30,27 @@ end version: String.trim(File.read!("VERSION")), ``` -## Usage +## Usage - Normal Apps ```bash $ mix eliver.bump ``` +## Usage - Umbrella Apps + +In umbrella apps there are two supported use-cases: + + 1. A single version file is provided, with all sub-apps sharing the same version number. + 2. Sub-app versions are managed seperately. In this case, each app has a seperate `VERSION` and `CHANGELOG.md` file. + + For option 1, eliver is used in the same manner as for normal Elixir apps. + + For option 2, eliver provides a `--multi` flag. + +```bash +$ mix eliver.bump --multi +``` + ## Contributing Please do. diff --git a/lib/eliver.ex b/lib/eliver.ex index 29908d9..8e9ee7a 100644 --- a/lib/eliver.ex +++ b/lib/eliver.ex @@ -3,6 +3,7 @@ defmodule Eliver do def next_version(version_number, bump_type) do [major, minor, patch] = String.split(version_number, ".") |> Enum.map(&String.to_integer/1) case bump_type do + :none -> [major, minor, patch] :major -> [major + 1, 0, 0] :minor -> [major, minor + 1, 0] :patch -> [major, minor, patch + 1] diff --git a/lib/eliver/git.ex b/lib/eliver/git.ex index 143fad5..6abfa32 100644 --- a/lib/eliver/git.ex +++ b/lib/eliver/git.ex @@ -41,8 +41,40 @@ defmodule Eliver.Git do git "tag", ["#{new_version}", "-a", "-m", "Version: #{new_version}"] end - def push!(new_version) do - git "push", ["-q", "origin", current_branch(), new_version] + def commit!(commit_changes) do + for change <- commit_changes do + git "add", "#{elem(change, 1)}/CHANGELOG.md" + git "add", "#{elem(change, 1)}/VERSION" + end + + umbrella_changes = Enum.find(commit_changes, + fn({app, app_path, current_version, new_version, changelog_entries}) -> + app == :umbrella + end + ) + app_changes = List.delete(commit_changes, umbrella_changes) + + git "commit", ["-m", aggregated_commit_message(umbrella_changes, app_changes)] + + Enum.map(commit_changes, fn({app, app_path, _current_version, new_version, changelog_entries}) -> + if app == :umbrella do + git "tag", ["#{new_version}", "-a", "-m", "Version: #{new_version}"] + + "#{new_version}" + else + git "tag", ["#{app_path}/#{new_version}", "-a", "-m", "Version(#{Atom.to_string(app)}): #{new_version}"] + + "#{app_path}/#{new_version}" + end + end) + end + + def push!(tags) when is_list(tags) do + attributes = ["-q", "origin", current_branch()] ++ tags + git "push", attributes + end + def push!(tag) do + git "push", ["-q", "origin", current_branch(), tag] end defp git(command, args) when is_list(args) do @@ -72,6 +104,33 @@ Version #{new_version}: #{Enum.map(changelog_entries, fn(x) -> "* " <> x end) |> Enum.join("\n")} """ end + defp aggregated_commit_message(umbrella_changes, app_changes) when not is_nil(app_changes) do + """ +Version #{elem(umbrella_changes, 3)}: + +#{Enum.map(elem(umbrella_changes, 4), fn(x) -> "* " <> x end) |> Enum.join("\n")} + +Nested Changes: + #{Enum.flat_map(app_changes, fn(app_change) -> + [nested_commit_version(elem(app_change, 1), elem(app_change, 3))] + ++ + nested_commit_messages(elem(app_change, 4)) + end) |> Enum.join("\n")} + """ + end + defp aggregated_commit_message(umbrella_changes, _app_changes) do + """ +Version #{elem(umbrella_changes, 3)}: + +#{Enum.map(elem(umbrella_changes, 4), fn(x) -> "* " <> x end) |> Enum.join("\n")} + """ + end + defp nested_commit_version(app, version) do + "\t#{app} - #{version}" + end + defp nested_commit_messages(change_strings) do + Enum.map(change_strings, fn(change_string) -> "\t\t" <> change_string end) + end -end \ No newline at end of file +end diff --git a/lib/eliver/multiple.ex b/lib/eliver/multiple.ex new file mode 100644 index 0000000..3a1cd50 --- /dev/null +++ b/lib/eliver/multiple.ex @@ -0,0 +1,11 @@ +defmodule Eliver.Multiple do + + def list_sub_apps() do + Mix.Project.apps_paths() + |> case do + nil -> {:error, :unknown_app_structure} + sub_apps -> {:ok, sub_apps} + end + end + +end diff --git a/lib/mix/tasks/eliver.bump.ex b/lib/mix/tasks/eliver.bump.ex index 22a3b1d..a73d535 100644 --- a/lib/mix/tasks/eliver.bump.ex +++ b/lib/mix/tasks/eliver.bump.ex @@ -6,29 +6,99 @@ defmodule Mix.Tasks.Eliver.Bump do args |> parse_args |> bump end - defp bump(_) do + defp bump(args) do case check_for_git_problems() do {:error, message} -> say message, :red {:ok} -> - {new_version, changelog_entries} = get_changes() - if allow_changes?(new_version, changelog_entries) do - make_changes(new_version, changelog_entries) + do_bump(args) + end + end + + defp do_bump(args) do + case Keyword.get(args, :multi) do + true -> bump_multiple() + nil -> bump_normal() + end + end + + defp bump_multiple() do + Eliver.Multiple.list_sub_apps() + |> case do + {:ok, sub_apps} -> + sub_apps = Map.put(sub_apps, :umbrella, ".") + + changes = Enum.map(sub_apps, fn + + {:umbrella, app_path} -> + # Note changes re required for the umbrella app + {current_version, new_version, changelog_entries} = get_changes(app_path, :normal) + {:umbrella, app_path, current_version, new_version, changelog_entries} + + {app, app_path} -> + + say "\n\n==============================================" + say "Bump details for #{Atom.to_string(app)}" + say "==============================================" + + get_changes(app_path, :multi) + |> case do + nil -> nil + {current_version, new_version, changelog_entries} -> + {app, app_path, current_version, new_version, changelog_entries} + end + + + end) + |> Enum.filter(fn(change) -> not is_nil(change) end) + + if allow_changes?(changes) do + make_changes(changes) end + + {:error, :unknown_app_structure} -> + say "Please note that only standard umbrella app directory structures are supported. Please refer to the documentation for details", :red + end + end + defp bump_normal() do + {current_version, new_version, changelog_entries} = get_changes() + if allow_changes?(new_version, changelog_entries) do + make_changes(new_version, changelog_entries) end end - defp get_changes do - {get_new_version(), get_changelog_entries()} + defp get_changes(root_for_changes \\ ".", bump_type \\ :normal) do + {current_version, new_version} = get_new_version(root_for_changes, bump_type) + + if current_version == new_version do + nil + else + {current_version, new_version, get_changelog_entries()} + end end + defp allow_changes?(changes) when is_list(changes) do + say "\n" + say "==============================================" + say "Summary of changes:" + say "==============================================" + + Enum.each(changes, fn(app_changes) -> + display_change(app_changes) + end) + + say "\n" + result = ask "Continue?", false + case result do + {:ok, value} -> value + {:error, _} -> false + end + end defp allow_changes?(new_version, changelog_entries) do current_version = Eliver.VersionFile.version say "\n" say "Summary of changes:" - say "Bumping version #{current_version} → #{new_version}", :green - say ("#{Enum.map(changelog_entries, fn(x) -> "* " <> x end) |> Enum.join("\n")}"), :green - say "\n" + display_change(current_version, new_version, changelog_entries) result = ask "Continue?", false case result do {:ok, value} -> value @@ -36,6 +106,29 @@ defmodule Mix.Tasks.Eliver.Bump do end end + defp display_change({app, app_path, current_version, new_version, changelog_entries}) do + say "\n#{Atom.to_string(app)} (#{app_path})" + say "==============================================" + display_change(current_version, new_version, changelog_entries) + end + defp display_change(current_version, new_version, changelog_entries) do + say "Bumping version #{current_version} → #{new_version}", :green + say ("#{Enum.map(changelog_entries, fn(x) -> "* " <> x end) |> Enum.join("\n")}"), :green + say "\n" + end + + defp make_changes(changes) when is_list(changes) do + for {app, app_path, current_version, new_version, changelog_entries} <- changes do + Eliver.VersionFile.bump(new_version, "#{app_path}/VERSION") + Eliver.ChangeLogFile.bump(new_version, changelog_entries, "#{app_path}/CHANGELOG.md") + end + + tags = Eliver.Git.commit!(changes) + + say "Pushing to origin..." + Eliver.Git.push!(tags) + end + defp make_changes(new_version, changelog_entries) do Eliver.VersionFile.bump(new_version) Eliver.ChangeLogFile.bump(new_version, changelog_entries) @@ -68,8 +161,11 @@ defmodule Mix.Tasks.Eliver.Bump do end end - defp get_new_version do - Eliver.VersionFile.version |> Eliver.next_version(get_bump_type()) + defp get_new_version(dir, type) do + { + Eliver.VersionFile.version("#{dir}/VERSION"), + Eliver.VersionFile.version("#{dir}/VERSION") |> Eliver.next_version(get_bump_type(type)) + } end defp get_changelog_entries do @@ -77,7 +173,21 @@ defmodule Mix.Tasks.Eliver.Bump do result end - defp get_bump_type do + defp get_bump_type(:multi) do + result = choose "Select release type", + patch: "Bug fixes, recommended for all", + minor: "New features, but backwards compatible", + major: "Breaking changes", + none: "None", + default: :none + + case result do + {:ok, value} -> value + {:error, _} -> get_bump_type(:multi) + end + end + + defp get_bump_type(:normal) do result = choose "Select release type", patch: "Bug fixes, recommended for all", minor: "New features, but backwards compatible", @@ -86,12 +196,12 @@ defmodule Mix.Tasks.Eliver.Bump do case result do {:ok, value} -> value - {:error, _} -> get_bump_type() + {:error, _} -> get_bump_type(:normal) end end defp parse_args(args) do - {_, args, _} = OptionParser.parse(args) + {args, _, _} = OptionParser.parse(args) args end end