Skip to main content

src/version_bump@plugins@github.erl

-module(version_bump@plugins@github).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/version_bump/plugins/github.gleam").
-export([is_prerelease/1, verify/2, plugin/0]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

?MODULEDOC(
    " The GitHub publish plugin.\n"
    "\n"
    " Mirrors `@semantic-release/github`'s core responsibility: authenticate with\n"
    " a GitHub token, resolve the target repository, and create a GitHub Release\n"
    " for the version semantic-release just determined.\n"
    "\n"
    " Hooks implemented:\n"
    "   - verify_conditions: a `GITHUB_TOKEN`/`GH_TOKEN` and a resolvable\n"
    "     repository URL must be present, otherwise the pipeline aborts.\n"
    "   - publish:           create the GitHub release and return it.\n"
    "   - success:           log that the release was published.\n"
    "\n"
    " Effectful work (HTTP, environment access) is kept in the hook bodies; the\n"
    " pure decision logic lives in helpers (`resolve_token`, `is_prerelease`,\n"
    " `verify`) so it can be unit-tested without a network or live environment.\n"
).

-file("src/version_bump/plugins/github.gleam", 150).
?DOC(" Log that the GitHub release was published. MVP: a single log line.\n").
-spec do_success(
    version_bump@config:plugin_spec(),
    version_bump@context:context()
) -> {ok, nil} | {error, version_bump@error:release_error()}.
do_success(_, Context) ->
    Message = case erlang:element(9, Context) of
        {some, Next_release} ->
            <<"Published GitHub release "/utf8,
                (erlang:element(4, Next_release))/binary>>;

        none ->
            <<"Published GitHub release"/utf8>>
    end,
    version_bump@logging:success(Message),
    {ok, nil}.

-file("src/version_bump/plugins/github.gleam", 226).
?DOC(
    " Map a transport-level error onto the plugin's namespace so failures are\n"
    " attributed to the GitHub plugin in aggregate reporting.\n"
).
-spec adapt_error(version_bump@error:release_error()) -> version_bump@error:release_error().
adapt_error(Err) ->
    {plugin_error, <<"github"/utf8>>, version_bump@error:to_string(Err)}.

-file("src/version_bump/plugins/github.gleam", 166).
?DOC(
    " Whether the upcoming release is a prerelease (i.e. its channel-tied version\n"
    " carries a prerelease identifier such as `-beta.1`).\n"
).
-spec is_prerelease(version_bump@release:next_release()) -> boolean().
is_prerelease(Next_release) ->
    gleam_stdlib:contains_string(erlang:element(2, Next_release), <<"-"/utf8>>).

-file("src/version_bump/plugins/github.gleam", 269).
-spec with_parsed_repo(
    binary(),
    fun(({binary(), binary()}) -> {ok, LBF} |
        {error, version_bump@error:release_error()})
) -> {ok, LBF} | {error, version_bump@error:release_error()}.
with_parsed_repo(Url, Next) ->
    case version_bump@github_api:parse_repo_url(Url) of
        {ok, Owner_repo} ->
            Next(Owner_repo);

        {error, Err} ->
            {error, adapt_error(Err)}
    end.

-file("src/version_bump/plugins/github.gleam", 259).
-spec with_next_release(
    version_bump@context:context(),
    fun((version_bump@release:next_release()) -> {ok, LBA} |
        {error, version_bump@error:release_error()})
) -> {ok, LBA} | {error, version_bump@error:release_error()}.
with_next_release(Context, Next) ->
    case erlang:element(9, Context) of
        {some, Next_release} ->
            Next(Next_release);

        none ->
            {error,
                {plugin_error,
                    <<"github"/utf8>>,
                    <<"No next release to publish to GitHub."/utf8>>}}
    end.

-file("src/version_bump/plugins/github.gleam", 189).
?DOC(" The configured repository URL, treating an empty/whitespace value as absent.\n").
-spec resolve_repo_url(version_bump@config:config()) -> gleam@option:option(binary()).
resolve_repo_url(Config) ->
    case erlang:element(2, Config) of
        {some, Url} ->
            case gleam@string:trim(Url) of
                <<""/utf8>> ->
                    none;

                Trimmed ->
                    {some, Trimmed}
            end;

        none ->
            none
    end.

-file("src/version_bump/plugins/github.gleam", 249).
-spec with_repo_url(
    version_bump@config:config(),
    fun((binary()) -> {ok, LAV} | {error, version_bump@error:release_error()})
) -> {ok, LAV} | {error, version_bump@error:release_error()}.
with_repo_url(Config, Next) ->
    case resolve_repo_url(Config) of
        {some, Url} ->
            Next(Url);

        none ->
            {error,
                {plugin_error,
                    <<"github"/utf8>>,
                    <<"No repository URL configured."/utf8>>}}
    end.

-file("src/version_bump/plugins/github.gleam", 217).
-spec non_empty(binary()) -> gleam@option:option(binary()).
non_empty(Value) ->
    case gleam@string:trim(Value) of
        <<""/utf8>> ->
            none;

        Trimmed ->
            {some, Trimmed}
    end.

-file("src/version_bump/plugins/github.gleam", 210).
?DOC(" Look up a key in the live process environment via `envoy`.\n").
-spec envoy_lookup(binary()) -> gleam@option:option(binary()).
envoy_lookup(Key) ->
    case envoy_ffi:get(Key) of
        {ok, Value} ->
            non_empty(Value);

        {error, _} ->
            none
    end.

-file("src/version_bump/plugins/github.gleam", 202).
?DOC(
    " Look up a key in the passed-in environment, treating empty/whitespace-only\n"
    " values as absent.\n"
).
-spec env_lookup(gleam@dict:dict(binary(), binary()), binary()) -> gleam@option:option(binary()).
env_lookup(Env, Key) ->
    case gleam_stdlib:map_get(Env, Key) of
        {ok, Value} ->
            non_empty(Value);

        {error, _} ->
            none
    end.

-file("src/version_bump/plugins/github.gleam", 173).
?DOC(
    " Resolve a GitHub token from the supplied environment, preferring\n"
    " `GITHUB_TOKEN` over `GH_TOKEN`, falling back to the live process environment\n"
    " when neither key is present in the passed-in dictionary.\n"
).
-spec resolve_token(gleam@dict:dict(binary(), binary())) -> gleam@option:option(binary()).
resolve_token(Env) ->
    case env_lookup(Env, <<"GITHUB_TOKEN"/utf8>>) of
        {some, Token} ->
            {some, Token};

        none ->
            case env_lookup(Env, <<"GH_TOKEN"/utf8>>) of
                {some, Token@1} ->
                    {some, Token@1};

                none ->
                    case envoy_lookup(<<"GITHUB_TOKEN"/utf8>>) of
                        {some, Token@2} ->
                            {some, Token@2};

                        none ->
                            envoy_lookup(<<"GH_TOKEN"/utf8>>)
                    end
            end
    end.

-file("src/version_bump/plugins/github.gleam", 234).
-spec with_token(
    gleam@dict:dict(binary(), binary()),
    fun((binary()) -> {ok, LAQ} | {error, version_bump@error:release_error()})
) -> {ok, LAQ} | {error, version_bump@error:release_error()}.
with_token(Env, Next) ->
    case resolve_token(Env) of
        {some, Token} ->
            Next(Token);

        none ->
            {error,
                {plugin_error,
                    <<"github"/utf8>>,
                    <<"No GitHub token found. Set the GITHUB_TOKEN or GH_TOKEN environment "/utf8,
                        "variable."/utf8>>}}
    end.

-file("src/version_bump/plugins/github.gleam", 137).
?DOC(
    " Collect the synchronous inputs `do_publish` needs, short-circuiting with a\n"
    " `PluginError` if any are missing or unparseable.\n"
).
-spec gather_publish_inputs(version_bump@context:context()) -> {ok,
        {binary(), binary(), binary(), version_bump@release:next_release()}} |
    {error, version_bump@error:release_error()}.
gather_publish_inputs(Context) ->
    with_token(
        erlang:element(3, Context),
        fun(Token) ->
            with_repo_url(
                erlang:element(4, Context),
                fun(Url) ->
                    with_next_release(
                        Context,
                        fun(Next_release) ->
                            with_parsed_repo(
                                Url,
                                fun(_use0) ->
                                    {Owner, Repo} = _use0,
                                    {ok, {Token, Owner, Repo, Next_release}}
                                end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/version_bump/plugins/github.gleam", 105).
?DOC(
    " Create a GitHub release for the upcoming version and return it.\n"
    "\n"
    " The synchronous pre-flight (token, repo URL, next release, owner/repo) is\n"
    " gathered first; only the HTTP create-release call is asynchronous.\n"
).
-spec do_publish(
    version_bump@config:plugin_spec(),
    version_bump@context:context()
) -> version_bump@task:task({ok,
        gleam@option:option(version_bump@release:release())} |
    {error, version_bump@error:release_error()}).
do_publish(_, Context) ->
    case gather_publish_inputs(Context) of
        {error, Err} ->
            version_bump_task_ffi:resolve({error, Err});

        {ok, {Token, Owner, Repo, Next_release}} ->
            Prerelease = is_prerelease(Next_release),
            Head = erlang:element(5, Next_release),
            _pipe = version_bump@github_api:create_release(
                Token,
                Owner,
                Repo,
                erlang:element(4, Next_release),
                erlang:element(2, Next_release),
                erlang:element(7, Next_release),
                Prerelease,
                Head
            ),
            version_bump_task_ffi:map(_pipe, fun(Result) -> case Result of
                        {ok, Release} ->
                            {ok, {some, Release}};

                        {error, Err@1} ->
                            {error, adapt_error(Err@1)}
                    end end)
    end.

-file("src/version_bump/plugins/github.gleam", 66).
?DOC(
    " PURE verification: given a resolved token (if any) and the config, decide\n"
    " whether the GitHub plugin can run. Separated out so it can be tested without\n"
    " touching the process environment.\n"
).
-spec verify(gleam@option:option(binary()), version_bump@config:config()) -> {ok,
        nil} |
    {error, version_bump@error:release_error()}.
verify(Token, Config) ->
    case Token of
        none ->
            {error,
                {plugin_error,
                    <<"github"/utf8>>,
                    <<"No GitHub token found. Set the GITHUB_TOKEN or GH_TOKEN environment "/utf8,
                        "variable."/utf8>>}};

        {some, _} ->
            case resolve_repo_url(Config) of
                none ->
                    {error,
                        {plugin_error,
                            <<"github"/utf8>>,
                            <<"No repository URL configured. Set `repositoryUrl` so the GitHub "/utf8,
                                "release can be created."/utf8>>}};

                {some, Url} ->
                    case version_bump@github_api:parse_repo_url(Url) of
                        {ok, _} ->
                            {ok, nil};

                        {error, _} ->
                            {error,
                                {plugin_error,
                                    <<"github"/utf8>>,
                                    <<"Could not parse a GitHub owner/repo from repository URL: "/utf8,
                                        Url/binary>>}}
                    end
            end
    end.

-file("src/version_bump/plugins/github.gleam", 48).
?DOC(
    " Ensure a GitHub token and a resolvable repository are available before the\n"
    " pipeline does any work.\n"
).
-spec do_verify_conditions(
    version_bump@config:plugin_spec(),
    version_bump@context:context()
) -> {ok, nil} | {error, version_bump@error:release_error()}.
do_verify_conditions(_, Context) ->
    case erlang:element(12, Context) of
        true ->
            {ok, nil};

        false ->
            Token = resolve_token(erlang:element(3, Context)),
            verify(Token, erlang:element(4, Context))
    end.

-file("src/version_bump/plugins/github.gleam", 35).
?DOC(" The plugin record, wiring up the GitHub hooks.\n").
-spec plugin() -> version_bump@plugin:plugin().
plugin() ->
    _record = version_bump@plugin:new(<<"github"/utf8>>),
    {plugin,
        erlang:element(2, _record),
        {some, fun do_verify_conditions/2},
        erlang:element(4, _record),
        erlang:element(5, _record),
        erlang:element(6, _record),
        erlang:element(7, _record),
        erlang:element(8, _record),
        {some, fun do_publish/2},
        {some, fun do_success/2},
        erlang:element(11, _record)}.