Packages

Conventional-Commits-driven semantic releases for Gleam: analyse commits, compute the next version, generate release notes, and publish to Hex/npm and GitHub. A native Gleam port of semantic-release.

Current section

Files

Jump to
version_bump src version_bump@forgejo_api.erl
Raw

src/version_bump@forgejo_api.erl

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