Current section
Files
Jump to
Current section
Files
src/version_bump@plugins@forgejo.erl
-module(version_bump@plugins@forgejo).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/version_bump/plugins/forgejo.gleam").
-export([is_prerelease/1, resolve_api_base/2, verify/2, plugin/0]).
-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(
" The Forgejo publish plugin (Codeberg and self-hosted Forgejo instances).\n"
"\n"
" The Forgejo counterpart of the `github` plugin: authenticate with a\n"
" Forgejo token, resolve the target repository and instance, and create a\n"
" Forgejo release for the version semantic-release just determined. Forgejo\n"
" runs Codeberg, so `plugins = [..., \"forgejo\"]` with a\n"
" `https://codeberg.org/...` repository URL is the common setup.\n"
"\n"
" Hooks implemented:\n"
" - verify_conditions: a `FORGEJO_TOKEN`/`GITEA_TOKEN` and a resolvable\n"
" repository URL must be present, otherwise the pipeline aborts.\n"
" - publish: create the Forgejo release and return it.\n"
" - success: log that the release was published.\n"
"\n"
" Unlike GitHub, Forgejo is self-hostable, so the API base URL is derived\n"
" from the repository URL's host — overridable with the `url` plugin option\n"
" or the `FORGEJO_URL`/`GITEA_URL` environment variables (useful when the git\n"
" remote host differs from the API host, e.g. an SSH alias or a custom port).\n"
"\n"
" Effectful work (HTTP, environment access) is kept in the hook bodies; the\n"
" pure decision logic lives in helpers (`resolve_token`, `resolve_api_base`,\n"
" `is_prerelease`, `verify`) so it can be unit-tested without a network or\n"
" live environment.\n"
).
-file("src/version_bump/plugins/forgejo.gleam", 163).
?DOC(" Log that the Forgejo release was published. MVP: a single log line.\n").
-spec do_success(
version_bump@config:plugin_spec(),
version_bump@context:context()
) -> {ok, nil} | {error, version_bump@error:release_error()}.
do_success(_, Context) ->
Message = case erlang:element(9, Context) of
{some, Next_release} ->
<<"Published Forgejo release "/utf8,
(erlang:element(4, Next_release))/binary>>;
none ->
<<"Published Forgejo release"/utf8>>
end,
version_bump@logging:success(Message),
{ok, nil}.
-file("src/version_bump/plugins/forgejo.gleam", 268).
?DOC(
" Map a transport-level error onto the plugin's namespace so failures are\n"
" attributed to the Forgejo plugin in aggregate reporting.\n"
).
-spec adapt_error(version_bump@error:release_error()) -> version_bump@error:release_error().
adapt_error(Err) ->
{plugin_error, <<"forgejo"/utf8>>, version_bump@error:to_string(Err)}.
-file("src/version_bump/plugins/forgejo.gleam", 179).
?DOC(
" Whether the upcoming release is a prerelease (i.e. its channel-tied version\n"
" carries a prerelease identifier such as `-beta.1`).\n"
).
-spec is_prerelease(version_bump@release:next_release()) -> boolean().
is_prerelease(Next_release) ->
gleam_stdlib:contains_string(erlang:element(2, Next_release), <<"-"/utf8>>).
-file("src/version_bump/plugins/forgejo.gleam", 259).
-spec non_empty(binary()) -> gleam@option:option(binary()).
non_empty(Value) ->
case gleam@string:trim(Value) of
<<""/utf8>> ->
none;
Trimmed ->
{some, Trimmed}
end.
-file("src/version_bump/plugins/forgejo.gleam", 252).
?DOC(" Look up a key in the live process environment via `envoy`.\n").
-spec envoy_lookup(binary()) -> gleam@option:option(binary()).
envoy_lookup(Key) ->
case envoy_ffi:get(Key) of
{ok, Value} ->
non_empty(Value);
{error, _} ->
none
end.
-file("src/version_bump/plugins/forgejo.gleam", 244).
?DOC(
" Look up a key in a string dictionary, treating empty/whitespace-only values\n"
" as absent.\n"
).
-spec env_lookup(gleam@dict:dict(binary(), binary()), binary()) -> gleam@option:option(binary()).
env_lookup(Env, Key) ->
case gleam_stdlib:map_get(Env, Key) of
{ok, Value} ->
non_empty(Value);
{error, _} ->
none
end.
-file("src/version_bump/plugins/forgejo.gleam", 223).
?DOC(
" Look up a key in the passed-in environment, falling back to the live\n"
" process environment via `envoy`.\n"
).
-spec resolve_env(gleam@dict:dict(binary(), binary()), binary()) -> gleam@option:option(binary()).
resolve_env(Env, Key) ->
case env_lookup(Env, Key) of
{some, Value} ->
{some, Value};
none ->
envoy_lookup(Key)
end.
-file("src/version_bump/plugins/forgejo.gleam", 197).
?DOC(
" An explicitly configured instance URL, if any: the `url` plugin option\n"
" first, then the `FORGEJO_URL`/`GITEA_URL` environment variables.\n"
).
-spec explicit_base_url(
version_bump@config:plugin_spec(),
gleam@dict:dict(binary(), binary())
) -> gleam@option:option(binary()).
explicit_base_url(Spec, Env) ->
case env_lookup(erlang:element(3, Spec), <<"url"/utf8>>) of
{some, Url} ->
{some, Url};
none ->
case resolve_env(Env, <<"FORGEJO_URL"/utf8>>) of
{some, Url@1} ->
{some, Url@1};
none ->
resolve_env(Env, <<"GITEA_URL"/utf8>>)
end
end.
-file("src/version_bump/plugins/forgejo.gleam", 188).
?DOC(
" The base URL of the Forgejo instance the release is created on.\n"
"\n"
" PURE: an explicit override (the `url` plugin option or the\n"
" `FORGEJO_URL`/`GITEA_URL` environment variables) wins; otherwise the host\n"
" from the repository URL is assumed to serve the API over https.\n"
).
-spec resolve_api_base(gleam@option:option(binary()), binary()) -> binary().
resolve_api_base(Explicit, Host) ->
case Explicit of
{some, Url} ->
gleam@string:trim(Url);
none ->
<<"https://"/utf8, Host/binary>>
end.
-file("src/version_bump/plugins/forgejo.gleam", 311).
-spec with_parsed_repo(
binary(),
fun((version_bump@repo_url:repo_ref()) -> {ok, LBF} |
{error, version_bump@error:release_error()})
) -> {ok, LBF} | {error, version_bump@error:release_error()}.
with_parsed_repo(Url, Next) ->
case version_bump@repo_url:parse(Url) of
{ok, Ref} ->
Next(Ref);
{error, nil} ->
{error,
{plugin_error,
<<"forgejo"/utf8>>,
<<"Could not parse a host/owner/repo from repository URL: "/utf8,
Url/binary>>}}
end.
-file("src/version_bump/plugins/forgejo.gleam", 301).
-spec with_next_release(
version_bump@context:context(),
fun((version_bump@release:next_release()) -> {ok, LBA} |
{error, version_bump@error:release_error()})
) -> {ok, LBA} | {error, version_bump@error:release_error()}.
with_next_release(Context, Next) ->
case erlang:element(9, Context) of
{some, Next_release} ->
Next(Next_release);
none ->
{error,
{plugin_error,
<<"forgejo"/utf8>>,
<<"No next release to publish to Forgejo."/utf8>>}}
end.
-file("src/version_bump/plugins/forgejo.gleam", 231).
?DOC(" The configured repository URL, treating an empty/whitespace value as absent.\n").
-spec resolve_repo_url(version_bump@config:config()) -> gleam@option:option(binary()).
resolve_repo_url(Config) ->
case erlang:element(2, Config) of
{some, Url} ->
case gleam@string:trim(Url) of
<<""/utf8>> ->
none;
Trimmed ->
{some, Trimmed}
end;
none ->
none
end.
-file("src/version_bump/plugins/forgejo.gleam", 291).
-spec with_repo_url(
version_bump@config:config(),
fun((binary()) -> {ok, LAV} | {error, version_bump@error:release_error()})
) -> {ok, LAV} | {error, version_bump@error:release_error()}.
with_repo_url(Config, Next) ->
case resolve_repo_url(Config) of
{some, Url} ->
Next(Url);
none ->
{error,
{plugin_error,
<<"forgejo"/utf8>>,
<<"No repository URL configured."/utf8>>}}
end.
-file("src/version_bump/plugins/forgejo.gleam", 214).
?DOC(
" Resolve a Forgejo token from the supplied environment, preferring\n"
" `FORGEJO_TOKEN` over `GITEA_TOKEN`, falling back to the live process\n"
" environment when neither key is present in the passed-in dictionary.\n"
).
-spec resolve_token(gleam@dict:dict(binary(), binary())) -> gleam@option:option(binary()).
resolve_token(Env) ->
case resolve_env(Env, <<"FORGEJO_TOKEN"/utf8>>) of
{some, Token} ->
{some, Token};
none ->
resolve_env(Env, <<"GITEA_TOKEN"/utf8>>)
end.
-file("src/version_bump/plugins/forgejo.gleam", 276).
-spec with_token(
gleam@dict:dict(binary(), binary()),
fun((binary()) -> {ok, LAQ} | {error, version_bump@error:release_error()})
) -> {ok, LAQ} | {error, version_bump@error:release_error()}.
with_token(Env, Next) ->
case resolve_token(Env) of
{some, Token} ->
Next(Token);
none ->
{error,
{plugin_error,
<<"forgejo"/utf8>>,
<<"No Forgejo token found. Set the FORGEJO_TOKEN or GITEA_TOKEN "/utf8,
"environment variable."/utf8>>}}
end.
-file("src/version_bump/plugins/forgejo.gleam", 147).
?DOC(
" Collect the synchronous inputs `do_publish` needs, short-circuiting with a\n"
" `PluginError` if any are missing or unparseable.\n"
).
-spec gather_publish_inputs(
version_bump@config:plugin_spec(),
version_bump@context:context()
) -> {ok,
{binary(),
binary(),
binary(),
binary(),
version_bump@release:next_release()}} |
{error, version_bump@error:release_error()}.
gather_publish_inputs(Spec, Context) ->
with_token(
erlang:element(3, Context),
fun(Token) ->
with_repo_url(
erlang:element(4, Context),
fun(Url) ->
with_next_release(
Context,
fun(Next_release) ->
with_parsed_repo(
Url,
fun(Ref) ->
{repo_ref, Host, Owner, Repo} = Ref,
Base_url = resolve_api_base(
explicit_base_url(
Spec,
erlang:element(3, Context)
),
Host
),
{ok,
{Token,
Base_url,
Owner,
Repo,
Next_release}}
end
)
end
)
end
)
end
).
-file("src/version_bump/plugins/forgejo.gleam", 114).
?DOC(
" Create a Forgejo release for the upcoming version and return it.\n"
"\n"
" The synchronous pre-flight (token, repo URL, next release, API base) is\n"
" gathered first; only the HTTP create-release call is asynchronous.\n"
).
-spec do_publish(
version_bump@config:plugin_spec(),
version_bump@context:context()
) -> version_bump@task:task({ok,
gleam@option:option(version_bump@release:release())} |
{error, version_bump@error:release_error()}).
do_publish(Spec, Context) ->
case gather_publish_inputs(Spec, Context) of
{error, Err} ->
version_bump_task_ffi:resolve({error, Err});
{ok, {Token, Base_url, Owner, Repo, Next_release}} ->
Prerelease = is_prerelease(Next_release),
Head = erlang:element(5, Next_release),
_pipe = version_bump@forgejo_api:create_release(
Base_url,
Token,
Owner,
Repo,
erlang:element(4, Next_release),
erlang:element(2, Next_release),
erlang:element(7, Next_release),
Prerelease,
Head
),
version_bump_task_ffi:map(_pipe, fun(Result) -> case Result of
{ok, Release} ->
{ok, {some, Release}};
{error, Err@1} ->
{error, adapt_error(Err@1)}
end end)
end.
-file("src/version_bump/plugins/forgejo.gleam", 75).
?DOC(
" PURE verification: given a resolved token (if any) and the config, decide\n"
" whether the Forgejo plugin can run. Separated out so it can be tested\n"
" without touching the process environment.\n"
).
-spec verify(gleam@option:option(binary()), version_bump@config:config()) -> {ok,
nil} |
{error, version_bump@error:release_error()}.
verify(Token, Config) ->
case Token of
none ->
{error,
{plugin_error,
<<"forgejo"/utf8>>,
<<"No Forgejo token found. Set the FORGEJO_TOKEN or GITEA_TOKEN "/utf8,
"environment variable."/utf8>>}};
{some, _} ->
case resolve_repo_url(Config) of
none ->
{error,
{plugin_error,
<<"forgejo"/utf8>>,
<<"No repository URL configured. Set `repositoryUrl` so the Forgejo "/utf8,
"release can be created."/utf8>>}};
{some, Url} ->
case version_bump@repo_url:parse(Url) of
{ok, _} ->
{ok, nil};
{error, _} ->
{error,
{plugin_error,
<<"forgejo"/utf8>>,
<<"Could not parse a host/owner/repo from repository URL: "/utf8,
Url/binary>>}}
end
end
end.
-file("src/version_bump/plugins/forgejo.gleam", 57).
?DOC(
" Ensure a Forgejo token and a resolvable repository are available before the\n"
" pipeline does any work.\n"
).
-spec do_verify_conditions(
version_bump@config:plugin_spec(),
version_bump@context:context()
) -> {ok, nil} | {error, version_bump@error:release_error()}.
do_verify_conditions(_, Context) ->
case erlang:element(12, Context) of
true ->
{ok, nil};
false ->
Token = resolve_token(erlang:element(3, Context)),
verify(Token, erlang:element(4, Context))
end.
-file("src/version_bump/plugins/forgejo.gleam", 44).
?DOC(" The plugin record, wiring up the Forgejo hooks.\n").
-spec plugin() -> version_bump@plugin:plugin().
plugin() ->
_record = version_bump@plugin:new(<<"forgejo"/utf8>>),
{plugin,
erlang:element(2, _record),
{some, fun do_verify_conditions/2},
erlang:element(4, _record),
erlang:element(5, _record),
erlang:element(6, _record),
erlang:element(7, _record),
erlang:element(8, _record),
{some, fun do_publish/2},
{some, fun do_success/2},
erlang:element(11, _record)}.