-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.