-module(version_bump@forgejo_api). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/version_bump/forgejo_api.gleam"). -export([build_release_payload/5, release_endpoint/3, create_release/9]). -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 Forgejo REST client used by the Forgejo publish plugin.\n" "\n" " Forgejo (the software Codeberg runs) exposes a Gitea-compatible REST API\n" " whose create-release endpoint mirrors GitHub's almost field-for-field. The\n" " key differences from `github_api`: the base URL is instance-dependent\n" " (Forgejo is self-hostable), and authentication uses the `token` scheme.\n" "\n" " Pure request-building helpers are separated from the effectful\n" " `create_release`, which actually performs the HTTP call. This keeps the\n" " parsing/serialisation logic unit-testable without a network.\n" ). -file("src/version_bump/forgejo_api.gleam", 30). ?DOC( " Serialise the JSON body for a create-release request.\n" "\n" " PURE: the `POST` payload for `{base}/api/v1/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/forgejo_api.gleam", 151). -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/forgejo_api.gleam", 52). ?DOC( " Build the create-release endpoint URL from the instance base URL.\n" "\n" " PURE. `base_url` is the root of the Forgejo instance (e.g.\n" " `https://codeberg.org` or `https://git.example.com:3000`); trailing slashes\n" " are tolerated.\n" ). -spec release_endpoint(binary(), binary(), binary()) -> binary(). release_endpoint(Base_url, Owner, Repo) -> <<<<<<<<<<(drop_trailing_slash(gleam@string:trim(Base_url)))/binary, "/api/v1/repos/"/utf8>>/binary, Owner/binary>>/binary, "/"/utf8>>/binary, Repo/binary>>/binary, "/releases"/utf8>>. -file("src/version_bump/forgejo_api.gleam", 160). ?DOC( " Parse the `html_url` field out of a Forgejo release JSON response, if\n" " 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/forgejo_api.gleam", 171). -spec http_error_to_string(gleam@httpc:http_error()) -> binary(). http_error_to_string(Err) -> case Err of invalid_utf8_response -> <<"Forgejo API returned a non-UTF-8 response"/utf8>>; response_timeout -> <<"Forgejo API request timed out"/utf8>>; {failed_to_connect, _, _} -> <<"Failed to connect to the Forgejo API"/utf8>> end. -file("src/version_bump/forgejo_api.gleam", 129). ?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>>, <<"token "/utf8, Token/binary>> ), _pipe@4 = gleam@http@request:set_header( _pipe@3, <<"accept"/utf8>>, <<"application/json"/utf8>> ), _pipe@5 = gleam@http@request:set_header( _pipe@4, <<"content-type"/utf8>>, <<"application/json"/utf8>> ), gleam@http@request:set_header( _pipe@5, <<"user-agent"/utf8>>, <<"version_bump"/utf8>> ) end}; {error, _} -> {error, <<"invalid Forgejo API URL: "/utf8, Url/binary>>} end. -file("src/version_bump/forgejo_api.gleam", 117). ?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/forgejo_api.gleam", 72). ?DOC( " Create a Forgejo 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(), binary(), boolean(), binary() ) -> version_bump@task:task({ok, version_bump@release:release()} | {error, version_bump@error:release_error()}). create_release( Base_url, Token, Owner, Repo, Tag, Name, Body, Prerelease, Target ) -> Url = release_endpoint(Base_url, Owner, Repo), 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 Forgejo API: "/utf8, Resp_body/binary>>}}; S when (S >= 200) andalso (S < 300) -> {ok, {release, Name, parse_html_url(Resp_body), Tag, Tag, none, <<"forgejo"/utf8>>}}; S@1 -> {error, {network_error, <<<<<<"Forgejo API responded with status "/utf8, (erlang:integer_to_binary(S@1))/binary>>/binary, ": "/utf8>>/binary, Resp_body/binary>>}} end end ).