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