diff --git a/src/rebar3_hex.erl b/src/rebar3_hex.erl index 41c69a1f..e797b352 100644 --- a/src/rebar3_hex.erl +++ b/src/rebar3_hex.erl @@ -1,6 +1,12 @@ -module(rebar3_hex). --export([init/1, gather_opts/2, get_required/2, task_args/1, repo_opt/0, help_opt/0]). +-export([ init/1 + , gather_opts/2 + , get_required/2 + , task_args/1 + , repo_opt/0 + , help_opt/0 + ]). init(State) -> lists:foldl(fun provider_init/2, {ok, State}, [rebar3_hex_user, @@ -17,13 +23,13 @@ init(State) -> provider_init(Module, {ok, State}) -> Module:init(State). -gather_opts(Targets, State) -> +gather_opts(Targets, State) -> {Args, _} = rebar_state:command_parsed_args(State), - lists:foldl(fun(T, Acc) -> + lists:foldl(fun(T, Acc) -> case proplists:get_value(T, Args, undefined) of - undefined -> + undefined -> Acc; - V -> + V -> maps:put(T, V, Acc) end end, #{}, Targets). diff --git a/src/rebar3_hex_client.erl b/src/rebar3_hex_client.erl index 9d37d1f4..5aaf0c68 100644 --- a/src/rebar3_hex_client.erl +++ b/src/rebar3_hex_client.erl @@ -1,18 +1,19 @@ -module(rebar3_hex_client). -export([ is_success/1 - , key_add/3 - , key_get/2 - , key_delete/2 - , key_delete_all/1 - , key_list/1 - , publish/3 - , publish_docs/4 - , delete_docs/3 - , test_key/2 - , member_of/1 - , pretty_print_status/1 - , pretty_print_errors/1]). + , key_add/3 + , key_get/2 + , key_delete/2 + , key_delete_all/1 + , key_list/1 + , publish/3 + , publish_docs/4 + , delete_docs/3 + , test_key/2 + , member_of/1 + , pretty_print_status/1 + , pretty_print_errors/1 + ]). -include("rebar3_hex.hrl"). @@ -30,7 +31,6 @@ key_get(HexConfig, <>) -> key_get(HexConfig, KeyName) -> key_get(HexConfig, to_binary(KeyName)). - member_of(HexConfig) -> Res = hex_api_organization:list(HexConfig), response(Res). diff --git a/src/rebar3_hex_config.erl b/src/rebar3_hex_config.erl index 36b622af..9a306e11 100644 --- a/src/rebar3_hex_config.erl +++ b/src/rebar3_hex_config.erl @@ -1,15 +1,15 @@ -module(rebar3_hex_config). --export([api_key_name/1 - , api_key_name/2 - , repos_key_name/0 - , org_key_name/2 - , parent_repos/1 - , hex_config/2 - , hex_config_write/1 - , hex_config_read/1 - , repo/1 - , update_auth_config/2 +-export([ api_key_name/1 + , api_key_name/2 + , repos_key_name/0 + , org_key_name/2 + , parent_repos/1 + , hex_config/2 + , hex_config_write/1 + , hex_config_read/1 + , repo/1 + , update_auth_config/2 ]). -include("rebar3_hex.hrl"). @@ -33,6 +33,7 @@ org_key_name(Key, Org) -> Prefix = key_name_prefix(Key), key_name(Prefix, <<"-repository-">>, Org). +-spec hostname() -> binary(). hostname() -> {ok, Name} = inet:gethostname(), list_to_binary(Name). @@ -55,12 +56,10 @@ repo(State) -> #{repos := Repos} = rebar_resource_v2:find_resource_state(pkg, Resources), case proplists:get_value(repo, Args, undefined) of undefined -> - DefaultBinName = rebar_utils:to_binary(?DEFAULT_HEX_REPO), - Res = lists:filter(fun(R) -> maps:get(name, R) =/= DefaultBinName end, - Repos), + Res = [R || R <- Repos, maps:get(name, R) =/= ?DEFAULT_HEX_REPO], case Res of [] -> - case rebar_hex_repos:get_repo_config(rebar_utils:to_binary(?DEFAULT_HEX_REPO), Repos) of + case rebar_hex_repos:get_repo_config(?DEFAULT_HEX_REPO, Repos) of {ok, Repo} -> {ok, Repo}; _ -> @@ -90,12 +89,13 @@ repo(State, RepoName) -> end. --define(ENV_VARS, [ - {"HEX_API_KEY", {api_key, {string, undefined}}}, - {"HEX_API_URL", {api_url, {string, undefined}}}, - {"HEX_UNSAFE_REGISTRY", {repo_verify, {boolean, false}}}, - {"HEX_NO_VERIFY_REPO_ORIGIN", {repo_verify_origin, {boolean, true}}} - ]). +-define( ENV_VARS + , [ {"HEX_API_KEY", {api_key, {string, undefined}}} + , {"HEX_API_URL", {api_url, {string, undefined}}} + , {"HEX_UNSAFE_REGISTRY", {repo_verify, {boolean, false}}} + , {"HEX_NO_VERIFY_REPO_ORIGIN", {repo_verify_origin, {boolean, true}}} + ] + ). merge_with_env(Repo) -> lists:foldl(fun({EnvName, {Key, _} = Default}, Acc) -> @@ -152,11 +152,10 @@ get_repo(BinaryName, Repos) -> {error,{rebar_hex_repos,{repo_not_found,BinaryName}}} -> undefined end. -hex_config(Repo, Op) -> - case Op of - read -> hex_config_read(Repo); - write -> hex_config_write(Repo) - end. +hex_config(Repo, read) -> + hex_config_read(Repo); +hex_config(Repo, write) -> + hex_config_write(Repo). hex_config_write(#{api_key := Key} = HexConfig) when is_binary(Key) -> {ok, HexConfig}; diff --git a/src/rebar3_hex_docs.erl b/src/rebar3_hex_docs.erl index 5e9d0ad3..c3dee2ed 100644 --- a/src/rebar3_hex_docs.erl +++ b/src/rebar3_hex_docs.erl @@ -8,9 +8,10 @@ -include("rebar3_hex.hrl"). -define(PROVIDER, docs). --define(DEPS, [{default, edoc}]). +-define(DEPS, [{default, lock}]). -define(ENDPOINT, "packages"). +-define(DEFAULT_DOC_DIR, "doc"). %% =================================================================== %% Public API @@ -46,20 +47,42 @@ do(State) -> ?PRV_ERROR(Reason) end. -%% The following function is made available and exported for publishing docs via the main the publish command. +%% @doc Publish documentation directory to repository +%% +%% This following function is exported for publishing docs via the +%% main the publish command. +-spec publish(rebar_app_info:t(), rebar_state:t(), map()) -> + {ok, rebar_state:t()}. publish(App, State, Repo) -> handle_command(App, State, Repo). -publish_apps(Apps, State) -> - lists:foldl(fun(App, {ok, StateAcc}) -> - case handle_command(App, StateAcc) of - {ok, _StateAcc} -> - {ok, StateAcc}; - Err -> - throw(Err) - end +-spec format_error(any()) -> iolist(). +format_error(bad_command) -> + "Invalid command and/or options provided"; +format_error({publish, {unauthorized, _Res}}) -> + "Error publishing : Not authorized"; +format_error({publish, {not_found, _Res}}) -> + "Error publishing : Package or Package Version not found"; +format_error({revert, {unauthorized, _Res}}) -> + "Error reverting docs : Not authorized"; +format_error({revert, {not_found, _Res}}) -> + "Error reverting docs : Package or Package Version not found"; +format_error(Reason) -> + rebar3_hex_error:format_error(Reason). - end, {ok, State}, Apps). +%% =================================================================== +%% Internal Functions +%% =================================================================== + +publish_apps(Apps, State) -> + lists:foldl(fun(App, {ok, StateAcc}) -> + case handle_command(App, StateAcc) of + {ok, _StateAcc} -> + {ok, StateAcc}; + Err -> + throw(Err) + end + end, {ok, State}, Apps). handle_command(App, State) -> {ok, Repo} = rebar3_hex_config:repo(State), @@ -67,8 +90,7 @@ handle_command(App, State) -> handle_command(App, State, Repo) -> {Args, _} = rebar_state:command_parsed_args(State), - Revert = proplists:get_value(revert, Args, undefined), - case Revert of + case proplists:get_value(revert, Args, undefined) of undefined -> do_publish(App, State, Repo); Vsn -> @@ -77,7 +99,9 @@ handle_command(App, State, Repo) -> do_publish(App, State, Repo) -> AppDir = rebar_app_info:dir(App), - Files = rebar3_hex_file:expand_paths(["doc"], AppDir), + DocDir = resolve_doc_dir(App), + assert_doc_dir(filename:join(AppDir, DocDir)), + Files = rebar3_hex_file:expand_paths([DocDir], AppDir), AppDetails = rebar_app_info:app_details(App), Name = binary_to_list(rebar_app_info:name(App)), PkgName = rebar_utils:to_list(proplists:get_value(pkg_name, AppDetails, Name)), @@ -85,7 +109,7 @@ do_publish(App, State, Repo) -> Vsn = rebar_utils:vcs_vsn(App, OriginalVsn, State), Tarball = PkgName ++ "-" ++ vsn_string(Vsn) ++ "-docs.tar.gz", - ok = erl_tar:create(Tarball, file_list(Files), [compressed]), + ok = erl_tar:create(Tarball, file_list(Files, DocDir), [compressed]), {ok, Tar} = file:read_file(Tarball), file:delete(Tarball), @@ -118,23 +142,32 @@ do_revert(App, State, Repo, Vsn) -> ?PRV_ERROR({revert, Reason}) end. -file_list(Files) -> - [{drop_path(ShortName, ["doc"]), FullName} || {ShortName, FullName} <- Files]. +%% @doc Returns the directory were docs are to be found +%% +%% The priority for resolution is the following: +%% 1. `doc' entry in the application's `*.app.src'. +%% 2. `dir' entry specified in `edoc_opts'. +%% 3. `"doc"' fallback default value. +-spec resolve_doc_dir(rebar_app_info:t()) -> string(). +resolve_doc_dir(AppInfo) -> + AppOpts = rebar_app_info:opts(AppInfo), + EdocOpts = rebar_opts:get(AppOpts, edoc_opts, []), + AppDetails = rebar_app_info:app_details(AppInfo), + Dir = proplists:get_value(dir, EdocOpts, ?DEFAULT_DOC_DIR), + proplists:get_value(doc, AppDetails, Dir). + +-spec assert_doc_dir(string()) -> true. +assert_doc_dir(DocDir) -> + filelib:is_dir(DocDir) orelse + rebar_api:abort( "Docs were not published since they " + "couldn't be found in '~s'. " + "Please build the docs and then run " + "`rebar3 hex docs` to publish them." + , [DocDir] + ). + +file_list(Files, DocDir) -> + [{drop_path(ShortName, [DocDir]), FullName} || {ShortName, FullName} <- Files]. drop_path(File, Path) -> filename:join(filename:split(File) -- Path). - --spec format_error(any()) -> iolist(). -format_error(bad_command) -> - "Invalid command and/or options provided"; -format_error({publish, {unauthorized, _Res}}) -> - "Error publishing : Not authorized"; -format_error({publish, {not_found, _Res}}) -> - "Error publishing : Package or Package Version not found"; -format_error({revert, {unauthorized, _Res}}) -> - "Error reverting docs : Not authorized"; -format_error({revert, {not_found, _Res}}) -> - "Error reverting docs : Package or Package Version not found"; -format_error(Reason) -> - rebar3_hex_error:format_error(Reason). - diff --git a/src/rebar3_hex_file.erl b/src/rebar3_hex_file.erl index b198e2ce..25cf516f 100644 --- a/src/rebar3_hex_file.erl +++ b/src/rebar3_hex_file.erl @@ -1,8 +1,7 @@ -module(rebar3_hex_file). --export([ - expand_paths/2, - update_app_src/2 +-export([ expand_paths/2 + , update_app_src/2 ]). expand_paths(Paths, Dir) -> diff --git a/src/rebar3_hex_publish.erl b/src/rebar3_hex_publish.erl index e58730c4..684da4ae 100644 --- a/src/rebar3_hex_publish.erl +++ b/src/rebar3_hex_publish.erl @@ -3,25 +3,28 @@ %% @end -module(rebar3_hex_publish). --export([init/1, - do/1, - format_error/1]). +-export([ init/1 + , do/1 + , format_error/1 + ]). --export([publish/3, - publish/8, - validate_app_details/1, - gather_deps/1]). +-export([ publish/3 + , publish/8 + , validate_app_details/1 + , gather_deps/1 + ]). -include("rebar3_hex.hrl"). -define(PROVIDER, publish). -define(DEPS, [{default, lock}]). --define(VALIDATIONS, [ has_semver - , has_contributors - , has_maintainers - , has_description - , has_licenses ]). +-define(VALIDATIONS, [ has_semver + , has_contributors + , has_maintainers + , has_description + , has_licenses + ]). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). @@ -34,7 +37,6 @@ -spec init(rebar_state:t()) -> {ok, rebar_state:t()}. init(State) -> - Provider = providers:create([{name, ?PROVIDER}, {module, ?MODULE}, {namespace, hex}, @@ -61,11 +63,11 @@ do(State) -> ?PRV_ERROR(Reason) end. -handle_command(#{revert := Vsn, package := Pkg}, State, Repo) -> +handle_command(#{revert := Vsn, package := Pkg}, State, Repo) -> ok = rebar3_hex_revert:revert(binarify(Pkg), binarify(Vsn), Repo, State), {ok, State}; -handle_command(#{revert := _Vsn}, _State, _Repo) -> +handle_command(#{revert := _Vsn}, _State, _Repo) -> {error, "--revert requires a package name"}; handle_command(_Args, State, Repo) -> @@ -238,7 +240,7 @@ lock_to_dep(_, Acc) -> publish_package_and_docs(Name, Version, Metadata, PackageFiles, HexConfig, App, State) -> {Args, _} = rebar_state:command_parsed_args(State), - HexOpts = hex_opts(Args), + HexOpts = hex_opts(Args), case rebar3_hex_config:hex_config_write(HexConfig) of {ok, HexConfig1} -> case create_and_publish(HexOpts, Metadata, PackageFiles, HexConfig1) of @@ -484,7 +486,7 @@ to_list(X) when erlang:is_list(X) -> X. -help(package) -> +help(package) -> "Specifies the package to use with the publish command, currently only utilized in a revert operation"; help(revert) -> "Revert given version, if the last version is reverted the package is removed"; @@ -492,7 +494,7 @@ help(replace) -> "Allows overwriting an existing package version if it exists. Private " "packages can always be overwritten, publicpackages can only be " "overwritten within one hour after they were initially published."; -help(yes) -> +help(yes) -> "Publishes the package without any confirmation prompts". support() -> diff --git a/test/rebar3_hex_integration_SUITE.erl b/test/rebar3_hex_integration_SUITE.erl index 302999b2..8393204d 100644 --- a/test/rebar3_hex_integration_SUITE.erl +++ b/test/rebar3_hex_integration_SUITE.erl @@ -9,54 +9,54 @@ %%%%%%%%%%%%%%%%%% all() -> - - [sanity_check - , decrypt_write_key_test - , bad_command_test - , docs_test - , docs_auth_error_test - , docs_invalid_repo_test - , docs_no_write_key_test - , docs_revert_test - , docs_revert_auth_error_test - , reset_password_test - , reset_password_error_test - , reset_password_unhandled_test - , reset_password_api_error_test - , register_user_test - , register_empty_password_test - , register_password_mismatch_test - , register_error_test - , register_existing_user_test - , auth_test - , auth_bad_local_password_test - , auth_password_24_char_test - , auth_password_32_char_test - , auth_unhandled_test - , auth_error_test - , whoami_test - , whoami_not_authed_test - , whoami_error_test - , whoami_unknown_test - , whoami_unhandled_test - , deauth_test - , publish_test - , publish_replace_test - , publish_revert_test - , publish_org_test - , publish_org_error_test - , publish_org_requires_repo_arg_test - , publish_error_test - , publish_unauthorized_test - , key_list_test - , key_get_test - , key_add_test - , key_delete_test - , key_delete_all_test - , owner_add_test - , owner_transfer_test - , owner_list_test - , owner_remove_test]. + [ sanity_check + , decrypt_write_key_test + , bad_command_test + , docs_test + , docs_auth_error_test + , docs_dir_error_test + , docs_invalid_repo_test + , docs_no_write_key_test + , docs_revert_test + , docs_revert_auth_error_test + , reset_password_test + , reset_password_error_test + , reset_password_unhandled_test + , reset_password_api_error_test + , register_user_test + , register_empty_password_test + , register_password_mismatch_test + , register_error_test + , register_existing_user_test + , auth_test + , auth_bad_local_password_test + , auth_password_24_char_test + , auth_password_32_char_test + , auth_unhandled_test + , auth_error_test + , whoami_test + , whoami_not_authed_test + , whoami_error_test + , whoami_unknown_test + , whoami_unhandled_test + , deauth_test + , publish_test + , publish_replace_test + , publish_revert_test + , publish_org_test + , publish_org_error_test + , publish_org_requires_repo_arg_test + , publish_error_test + , publish_unauthorized_test + , key_list_test + , key_get_test + , key_add_test + , key_delete_test + , key_delete_all_test + , owner_add_test + , owner_transfer_test + , owner_list_test + , owner_remove_test]. init_per_suite(Config) -> meck:new([hex_api_user, rebar3_hex_config, rebar3_hex_io], [passthrough, no_link, unstick]), @@ -123,6 +123,13 @@ docs_test(Config) -> {ok, NewState} = rebar_prv_edoc:do(DocState), ?assertMatch({ok, NewState}, rebar3_hex_docs:do(NewState)). +docs_dir_error_test(Config) -> + P = #{app => "valid", mocks => [docs]}, + {ok, #{rebar_state := State, repo := Repo}} = setup_state(P, Config), + Command = ["docs"], + {ok, NewState} = test_utils:mock_command(rebar3_hex_docs, Command, Repo, State), + ?assertThrow(rebar_abort, rebar3_hex_docs:do(NewState)). + docs_revert_test(Config) -> P = #{app => "valid", mocks => [docs]}, {ok, #{rebar_state := State, repo := Repo}} = setup_state(P, Config), @@ -421,18 +428,20 @@ publish_test(Config) -> P = #{app => "valid", mocks => [publish]}, {ok, #{rebar_state := State, repo := Repo}} = setup_state(P, Config), {ok, PubState} = test_utils:mock_command(rebar3_hex_publish, [], Repo, State), + {ok, _} = rebar_prv_edoc:do(PubState), ?assertMatch({ok, PubState}, rebar3_hex_publish:do(PubState)). %% TODO: This test currently is merely to see if we can handle the --replace switch %% In order for the test to be more meaningful we need to update the hex_api_model to keep %% track of packages that have been published and when, further we need to provide -%% a package add function on hex_api_model which takes a package name, published at timestamp, etc. +%% a package add function on hex_api_model which takes a package name, published at timestamp, etc. %% so we can test the sad paths (i.e., you can not replace a package after N seconds) publish_replace_test(Config) -> P = #{app => "valid", mocks => [publish]}, {ok, #{rebar_state := State, repo := Repo}} = setup_state(P, Config), {ok, PubState} = test_utils:mock_command(rebar3_hex_publish, ["--replace"], Repo, State), + {ok, _} = rebar_prv_edoc:do(PubState), ?assertMatch({ok, PubState}, rebar3_hex_publish:do(PubState)). @@ -455,6 +464,7 @@ publish_org_test(Config) -> }, {ok, #{rebar_state := State, repo := Repo}} = setup_state(P, Config), {ok, PubState} = test_utils:mock_command(rebar3_hex_publish, ["-r", "hexpm:valid"], Repo, State), + {ok, _} = rebar_prv_edoc:do(PubState), ?assertMatch({ok, PubState}, rebar3_hex_publish:do(PubState)). @@ -599,6 +609,9 @@ setup_state(P, Config) -> {ok, App, State} = test_utils:mock_app(AppName, ?config(data_dir, Config), Repo), + %% Make sure there is no leftover generated doc directory + ok = rebar_file_utils:rm_rf(filename:join(rebar_app_info:dir(App), "doc")), + Setup = Params#{ repo => Repo, app_state => App, rebar_state => State