-module(version_bump@plugins@exec).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/version_bump/plugins/exec.gleam").
-export([parse_release_type/1, 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 `exec` plugin — semantic-release's escape hatch.\n"
"\n"
" Instead of implementing a hook in Gleam, the user supplies a shell command\n"
" for any lifecycle step via `PluginSpec.options`. Each option key maps to one\n"
" hook; when present, that hook runs the command through `sh -c` in the\n"
" project's `cwd`:\n"
"\n"
" - `verifyConditionsCmd` -> verify_conditions\n"
" - `analyzeCommitsCmd` -> analyze_commits\n"
" - `verifyReleaseCmd` -> verify_release\n"
" - `generateNotesCmd` -> generate_notes\n"
" - `prepareCmd` -> prepare\n"
" - `publishCmd` -> publish\n"
" - `successCmd` -> success\n"
" - `failCmd` -> fail\n"
"\n"
" Hook semantics:\n"
" - analyze_commits: the trimmed stdout is parsed into a `ReleaseType`\n"
" (\"major\"/\"minor\"/\"patch\" -> `Some(..)`, empty / anything else -> `None`).\n"
" - generate_notes: the trimmed stdout becomes the release notes.\n"
" - publish: currently signals \"not handled\" (`None`) on success,\n"
" since the command produces no structured `Release`.\n"
" - everything else: a non-zero exit aborts the pipeline with a `PluginError`.\n"
).
-file("src/version_bump/plugins/exec.gleam", 198).
?DOC(
" Execute `cmd` through `sh -c` in `cwd`, returning captured stdout (with\n"
" stderr folded in by shellout's default) or a `PluginError` carrying the exit\n"
" code and output on failure.\n"
).
-spec run(binary(), binary()) -> {ok, binary()} |
{error, version_bump@error:release_error()}.
run(Cmd, Cwd) ->
_pipe = shellout:command(<<"sh"/utf8>>, [<<"-c"/utf8>>, Cmd], Cwd, []),
gleam@result:map_error(
_pipe,
fun(Failure) ->
{Code, Message} = Failure,
{plugin_error,
<<"exec"/utf8>>,
<<<<<<<<<<"command `"/utf8, Cmd/binary>>/binary,
"` failed (exit "/utf8>>/binary,
(erlang:integer_to_binary(Code))/binary>>/binary,
"): "/utf8>>/binary,
(gleam@string:trim(Message))/binary>>}
end
).
-file("src/version_bump/plugins/exec.gleam", 170).
?DOC(" `Some(value)` when `key` is present in the options dict, else `None`.\n").
-spec get_option(gleam@dict:dict(binary(), binary()), binary()) -> gleam@option:option(binary()).
get_option(Options, Key) ->
case gleam_stdlib:map_get(Options, Key) of
{ok, Value} ->
{some, Value};
{error, _} ->
none
end.
-file("src/version_bump/plugins/exec.gleam", 158).
?DOC(
" Look up the command string for an option key, treating a present-but-blank\n"
" value the same as an absent key (`None`).\n"
).
-spec command_for(version_bump@config:plugin_spec(), binary()) -> gleam@option:option(binary()).
command_for(Spec, Key) ->
case get_option(erlang:element(3, Spec), Key) of
{some, Cmd} ->
case gleam@string:trim(Cmd) of
<<""/utf8>> ->
none;
_ ->
{some, Cmd}
end;
none ->
none
end.
-file("src/version_bump/plugins/exec.gleam", 181).
?DOC(
" Run the command for the given option key only for its exit status; a\n"
" non-zero exit becomes a `PluginError`. A missing key is a successful no-op.\n"
).
-spec run_for_effect(
version_bump@config:plugin_spec(),
version_bump@context:context(),
binary()
) -> {ok, nil} | {error, version_bump@error:release_error()}.
run_for_effect(Spec, Context, Key) ->
case command_for(Spec, Key) of
none ->
{ok, nil};
{some, Cmd} ->
gleam@result:map(
run(Cmd, erlang:element(2, Context)),
fun(_) -> nil end
)
end.
-file("src/version_bump/plugins/exec.gleam", 135).
?DOC(" Run `failCmd` for effect; absent key is a no-op.\n").
-spec fail(version_bump@config:plugin_spec(), version_bump@context:context()) -> {ok,
nil} |
{error, version_bump@error:release_error()}.
fail(Spec, Context) ->
run_for_effect(Spec, Context, <<"failCmd"/utf8>>).
-file("src/version_bump/plugins/exec.gleam", 130).
?DOC(" Run `successCmd` for effect; absent key is a no-op.\n").
-spec success(version_bump@config:plugin_spec(), version_bump@context:context()) -> {ok,
nil} |
{error, version_bump@error:release_error()}.
success(Spec, Context) ->
run_for_effect(Spec, Context, <<"successCmd"/utf8>>).
-file("src/version_bump/plugins/exec.gleam", 116).
?DOC(
" Run `publishCmd` for effect. The command yields no structured `Release`, so\n"
" a successful run reports \"not handled\" (`None`); the engine still treats the\n"
" step as having run.\n"
).
-spec 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()}).
publish(Spec, Context) ->
version_bump_task_ffi:resolve(
case command_for(Spec, <<"publishCmd"/utf8>>) of
none ->
{ok, none};
{some, Cmd} ->
gleam@result:map(
run(Cmd, erlang:element(2, Context)),
fun(_) -> none end
)
end
).
-file("src/version_bump/plugins/exec.gleam", 109).
?DOC(" Run `prepareCmd` for effect; absent key is a no-op.\n").
-spec prepare(version_bump@config:plugin_spec(), version_bump@context:context()) -> {ok,
nil} |
{error, version_bump@error:release_error()}.
prepare(Spec, Context) ->
run_for_effect(Spec, Context, <<"prepareCmd"/utf8>>).
-file("src/version_bump/plugins/exec.gleam", 95).
?DOC(
" Run `generateNotesCmd`; its trimmed stdout is the notes. With no command the\n"
" plugin contributes no notes (the empty string).\n"
).
-spec generate_notes(
version_bump@config:plugin_spec(),
version_bump@context:context()
) -> {ok, binary()} | {error, version_bump@error:release_error()}.
generate_notes(Spec, Context) ->
case command_for(Spec, <<"generateNotesCmd"/utf8>>) of
none ->
{ok, <<""/utf8>>};
{some, Cmd} ->
gleam@result:map(
run(Cmd, erlang:element(2, Context)),
fun(Stdout) -> gleam@string:trim(Stdout) end
)
end.
-file("src/version_bump/plugins/exec.gleam", 86).
?DOC(" Run `verifyReleaseCmd` for effect; absent key is a no-op.\n").
-spec verify_release(
version_bump@config:plugin_spec(),
version_bump@context:context()
) -> {ok, nil} | {error, version_bump@error:release_error()}.
verify_release(Spec, Context) ->
run_for_effect(Spec, Context, <<"verifyReleaseCmd"/utf8>>).
-file("src/version_bump/plugins/exec.gleam", 147).
?DOC(
" Parse the trimmed stdout of an `analyzeCommitsCmd` into a `ReleaseType`.\n"
"\n"
" PURE. Matches semantic-release/exec semantics: the command prints the bump\n"
" type on stdout. `\"major\"`/`\"minor\"`/`\"patch\"` (case-insensitive, surrounding\n"
" whitespace ignored) map to `Some(..)`; empty output or any other value means\n"
" \"no release\" (`None`).\n"
).
-spec parse_release_type(binary()) -> gleam@option:option(version_bump@semver:release_type()).
parse_release_type(Stdout) ->
case string:lowercase(gleam@string:trim(Stdout)) of
<<"major"/utf8>> ->
{some, major};
<<"minor"/utf8>> ->
{some, minor};
<<"patch"/utf8>> ->
{some, patch};
_ ->
none
end.
-file("src/version_bump/plugins/exec.gleam", 72).
?DOC(
" Run `analyzeCommitsCmd` and parse its stdout into a `ReleaseType`. With no\n"
" command configured the plugin contributes no opinion (`None`).\n"
).
-spec analyze_commits(
version_bump@config:plugin_spec(),
version_bump@context:context()
) -> {ok, gleam@option:option(version_bump@semver:release_type())} |
{error, version_bump@error:release_error()}.
analyze_commits(Spec, Context) ->
case command_for(Spec, <<"analyzeCommitsCmd"/utf8>>) of
none ->
{ok, none};
{some, Cmd} ->
gleam@result:map(
run(Cmd, erlang:element(2, Context)),
fun(Stdout) -> parse_release_type(Stdout) end
)
end.
-file("src/version_bump/plugins/exec.gleam", 63).
?DOC(" Run `verifyConditionsCmd` for effect; absent key is a no-op.\n").
-spec verify_conditions(
version_bump@config:plugin_spec(),
version_bump@context:context()
) -> {ok, nil} | {error, version_bump@error:release_error()}.
verify_conditions(Spec, Context) ->
run_for_effect(Spec, Context, <<"verifyConditionsCmd"/utf8>>).
-file("src/version_bump/plugins/exec.gleam", 46).
?DOC(
" Build the `exec` plugin, wiring every hook to the command runner. Each hook\n"
" looks up its corresponding option key at call time; if the key is absent the\n"
" hook is a no-op (it returns the neutral value for that step), so a single\n"
" plugin record can serve any subset of configured commands.\n"
).
-spec plugin() -> version_bump@plugin:plugin().
plugin() ->
_record = version_bump@plugin:new(<<"exec"/utf8>>),
{plugin,
erlang:element(2, _record),
{some, fun verify_conditions/2},
{some, fun analyze_commits/2},
{some, fun verify_release/2},
{some, fun generate_notes/2},
erlang:element(7, _record),
{some, fun prepare/2},
{some, fun publish/2},
{some, fun success/2},
{some, fun fail/2}}.