-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", 144). ?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", 155). -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", 133). ?DOC( " Extract `(owner, repo)` from an HTTPS or `git@` GitHub remote URL.\n" "\n" " PURE. Parsing lives in `repo_url` (shared with the `forgejo` plugin); this\n" " keeps only the pair GitHub needs and maps failure onto `NetworkError`.\n" ). -spec parse_repo_url(binary()) -> {ok, {binary(), binary()}} | {error, version_bump@error:release_error()}. parse_repo_url(Url) -> case version_bump@repo_url:parse(Url) of {ok, {repo_ref, _, Owner, Repo}} -> {ok, {Owner, Repo}}; {error, nil} -> {error, {network_error, <<"Could not parse owner/repo from URL: "/utf8, Url/binary>>}} end.