-module(version_bump@github_api).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/version_bump/github_api.gleam").
-export([build_release_payload/5, create_release/8, parse_repo_url/1]).
-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(
" Minimal GitHub REST client used by the GitHub publish plugin.\n"
"\n"
" Pure request-building and URL-parsing helpers are separated from the\n"
" effectful `create_release`, which actually performs the HTTP call. This\n"
" keeps the parsing/serialisation logic unit-testable without a network.\n"
).
-file("src/version_bump/github_api.gleam", 27).
?DOC(
" Serialise the JSON body for a create-release request.\n"
"\n"
" PURE: the `POST` payload for `api.github.com/repos/{owner}/{repo}/releases`.\n"
).
-spec build_release_payload(binary(), binary(), binary(), boolean(), binary()) -> binary().
build_release_payload(Tag, Name, Body, Prerelease, Target) ->
_pipe = gleam@json:object(
[{<<"tag_name"/utf8>>, gleam@json:string(Tag)},
{<<"name"/utf8>>, gleam@json:string(Name)},
{<<"body"/utf8>>, gleam@json:string(Body)},
{<<"prerelease"/utf8>>, gleam@json:bool(Prerelease)},
{<<"target_commitish"/utf8>>, gleam@json:string(Target)}]
),
gleam@json:to_string(_pipe).
-file("src/version_bump/github_api.gleam", 234).
?DOC(" Parse the `html_url` field out of a GitHub release JSON response, if present.\n").
-spec parse_html_url(binary()) -> gleam@option:option(binary()).
parse_html_url(Body) ->
Decoder = begin
gleam@dynamic@decode:field(
<<"html_url"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Url) -> gleam@dynamic@decode:success(Url) end
)
end,
case gleam@json:parse(Body, Decoder) of
{ok, Url@1} ->
{some, Url@1};
{error, _} ->
none
end.
-file("src/version_bump/github_api.gleam", 245).
-spec http_error_to_string(gleam@httpc:http_error()) -> binary().
http_error_to_string(Err) ->
case Err of
invalid_utf8_response ->
<<"GitHub API returned a non-UTF-8 response"/utf8>>;
response_timeout ->
<<"GitHub API request timed out"/utf8>>;
{failed_to_connect, _, _} ->
<<"Failed to connect to the GitHub API"/utf8>>
end.
-file("src/version_bump/github_api.gleam", 108).
?DOC(" Build the `httpc` request from primitives (Erlang target only).\n").
-spec build_erlang_request(binary(), binary(), binary()) -> {ok,
gleam@http@request:request(binary())} |
{error, binary()}.
build_erlang_request(Url, Token, Body) ->
case gleam@http@request:to(Url) of
{ok, Req} ->
{ok,
begin
_pipe = Req,
_pipe@1 = gleam@http@request:set_method(_pipe, post),
_pipe@2 = gleam@http@request:set_body(_pipe@1, Body),
_pipe@3 = gleam@http@request:set_header(
_pipe@2,
<<"authorization"/utf8>>,
<<"Bearer "/utf8, Token/binary>>
),
_pipe@4 = gleam@http@request:set_header(
_pipe@3,
<<"accept"/utf8>>,
<<"application/vnd.github+json"/utf8>>
),
_pipe@5 = gleam@http@request:set_header(
_pipe@4,
<<"content-type"/utf8>>,
<<"application/json"/utf8>>
),
_pipe@6 = gleam@http@request:set_header(
_pipe@5,
<<"x-github-api-version"/utf8>>,
<<"2022-11-28"/utf8>>
),
gleam@http@request:set_header(
_pipe@6,
<<"user-agent"/utf8>>,
<<"version_bump"/utf8>>
)
end};
{error, _} ->
{error, <<"invalid GitHub API URL: "/utf8, Url/binary>>}
end.
-file("src/version_bump/github_api.gleam", 96).
?DOC(
" Perform the POST and yield `#(status_code, body)`. A status of `0` signals a\n"
" transport-level failure, with the message in the body slot.\n"
"\n"
" This function has a Gleam body (the Erlang/`httpc` implementation, also used\n"
" by any target without an external) and a JavaScript `@external` that uses\n"
" `fetch`. That is how one call site stays target-agnostic while the actual I/O\n"
" is synchronous on the BEAM and promise-based on Node.\n"
).
-spec send(binary(), binary(), binary()) -> version_bump@task:task({integer(),
binary()}).
send(Url, Token, Body) ->
case build_erlang_request(Url, Token, Body) of
{error, Message} ->
version_bump_task_ffi:resolve({0, Message});
{ok, Req} ->
case gleam@httpc:send(Req) of
{ok, Resp} ->
version_bump_task_ffi:resolve(
{erlang:element(2, Resp), erlang:element(4, Resp)}
);
{error, Err} ->
version_bump_task_ffi:resolve(
{0, http_error_to_string(Err)}
)
end
end.
-file("src/version_bump/github_api.gleam", 51).
?DOC(
" Create a GitHub release and return the resulting `Release`, asynchronously.\n"
"\n"
" The HTTP send is cross-target via `send`: on Erlang it uses `httpc`\n"
" synchronously; on JavaScript it uses `fetch` (a real promise). Both yield a\n"
" `Task(#(status, body))`, which is mapped here into a `Release` (parsing\n"
" `html_url`), a non-2xx `NetworkError`, or a transport `NetworkError`\n"
" (signalled as status `0`).\n"
).
-spec create_release(
binary(),
binary(),
binary(),
binary(),
binary(),
binary(),
boolean(),
binary()
) -> version_bump@task:task({ok, version_bump@release:release()} |
{error, version_bump@error:release_error()}).
create_release(Token, Owner, Repo, Tag, Name, Body, Prerelease, Target) ->
Url = <<<<<<<<<<<<"https://"/utf8, "api.github.com"/utf8>>/binary,
"/repos/"/utf8>>/binary,
Owner/binary>>/binary,
"/"/utf8>>/binary,
Repo/binary>>/binary,
"/releases"/utf8>>,
Payload = build_release_payload(Tag, Name, Body, Prerelease, Target),
version_bump_task_ffi:map(
send(Url, Token, Payload),
fun(Outcome) ->
{Status, Resp_body} = Outcome,
case Status of
0 ->
{error,
{network_error,
<<"Failed to reach the GitHub API: "/utf8,
Resp_body/binary>>}};
S when (S >= 200) andalso (S < 300) ->
{ok,
{release,
Name,
parse_html_url(Resp_body),
Tag,
Tag,
none,
<<"github"/utf8>>}};
S@1 ->
{error,
{network_error,
<<<<<<"GitHub API responded with status "/utf8,
(erlang:integer_to_binary(S@1))/binary>>/binary,
": "/utf8>>/binary,
Resp_body/binary>>}}
end
end
).
-file("src/version_bump/github_api.gleam", 226).
-spec first_segment(binary()) -> binary().
first_segment(S) ->
case gleam@string:split_once(S, <<"/"/utf8>>) of
{ok, {Head, _}} ->
Head;
{error, _} ->
S
end.
-file("src/version_bump/github_api.gleam", 212).
-spec drop_trailing_slash(binary()) -> binary().
drop_trailing_slash(S) ->
case gleam_stdlib:string_ends_with(S, <<"/"/utf8>>) of
true ->
drop_trailing_slash(gleam@string:drop_end(S, 1));
false ->
S
end.
-file("src/version_bump/github_api.gleam", 219).
-spec strip_git_suffix(binary()) -> binary().
strip_git_suffix(S) ->
case gleam_stdlib:string_ends_with(S, <<".git"/utf8>>) of
true ->
gleam@string:drop_end(S, 4);
false ->
S
end.
-file("src/version_bump/github_api.gleam", 205).
-spec drop_leading_slash(binary()) -> binary().
drop_leading_slash(S) ->
case S of
<<"/"/utf8, Rest/binary>> ->
drop_leading_slash(Rest);
_ ->
S
end.
-file("src/version_bump/github_api.gleam", 193).
?DOC(" Strip an optional `user@` prefix from a host portion (e.g. `git@github.com`).\n").
-spec strip_userinfo(binary()) -> binary().
strip_userinfo(Rest) ->
case gleam@string:split_once(Rest, <<"@"/utf8>>) of
{ok, {Before, After}} ->
case gleam_stdlib:contains_string(Before, <<"/"/utf8>>) of
true ->
Rest;
false ->
After
end;
{error, _} ->
Rest
end.
-file("src/version_bump/github_api.gleam", 137).
?DOC(
" Extract `(owner, repo)` from an HTTPS or `git@` GitHub remote URL.\n"
"\n"
" PURE. Handles the common forms:\n"
" - `https://github.com/owner/repo.git`\n"
" - `https://github.com/owner/repo`\n"
" - `git@github.com:owner/repo.git`\n"
" - `ssh://git@github.com/owner/repo.git`\n"
" The trailing `.git` and any trailing slash are stripped.\n"
).
-spec parse_repo_url(binary()) -> {ok, {binary(), binary()}} |
{error, version_bump@error:release_error()}.
parse_repo_url(Url) ->
Trimmed = gleam@string:trim(Url),
Without_prefix = case Trimmed of
<<"git+"/utf8, Rest/binary>> ->
Rest;
Other ->
Other
end,
Remainder = case Without_prefix of
<<"https://"/utf8, Rest@1/binary>> ->
strip_userinfo(Rest@1);
<<"http://"/utf8, Rest@2/binary>> ->
strip_userinfo(Rest@2);
<<"ssh://"/utf8, Rest@3/binary>> ->
strip_userinfo(Rest@3);
<<"git://"/utf8, Rest@4/binary>> ->
strip_userinfo(Rest@4);
<<"git@"/utf8, Rest@5/binary>> ->
Rest@5;
Other@1 ->
Other@1
end,
Path = case gleam@string:split_once(Remainder, <<":"/utf8>>) of
{ok, {_, After}} ->
After;
{error, _} ->
case gleam@string:split_once(Remainder, <<"/"/utf8>>) of
{ok, {_, After@1}} ->
After@1;
{error, _} ->
Remainder
end
end,
Path@1 = drop_leading_slash(Path),
Path@2 = strip_git_suffix(Path@1),
Path@3 = drop_trailing_slash(Path@2),
case gleam@string:split_once(Path@3, <<"/"/utf8>>) of
{ok, {Owner, Repo}} ->
case {Owner, Repo} of
{<<""/utf8>>, _} ->
{error,
{network_error,
<<"Could not parse owner/repo from URL: "/utf8,
Url/binary>>}};
{_, <<""/utf8>>} ->
{error,
{network_error,
<<"Could not parse owner/repo from URL: "/utf8,
Url/binary>>}};
{_, _} ->
Repo@1 = first_segment(Repo),
case Repo@1 of
<<""/utf8>> ->
{error,
{network_error,
<<"Could not parse owner/repo from URL: "/utf8,
Url/binary>>}};
_ ->
{ok, {Owner, Repo@1}}
end
end;
{error, _} ->
{error,
{network_error,
<<"Could not parse owner/repo from URL: "/utf8, Url/binary>>}}
end.