Skip to main content

src/version_bump.erl

-module(version_bump).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/version_bump.gleam").
-export([apply_dry_run/2, parse_args/1, main/0]).
-export_type([command/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(
    " CLI entrypoint for version_bump, a native Gleam port of semantic-release.\n"
    "\n"
    " The default invocation runs the full release pipeline against the working\n"
    " directory (`--cwd <path>`, default `.`):\n"
    "\n"
    "   1. read every environment variable into a `Dict(String, String)`\n"
    "   2. load configuration from the project (`config.load`)\n"
    "   3. apply CLI overrides (e.g. `--dry-run`)\n"
    "   4. run `engine.run` and report the resulting `Summary`\n"
    "\n"
    " On error the formatted message is printed and the process exits non-zero.\n"
    " `--version`/`version` prints the tool version; `--help`/`-h` prints usage.\n"
).

-type command() :: {release, binary(), boolean()} | show_version | show_help.

-file("src/version_bump.gleam", 149).
-spec usage() -> binary().
usage() ->
    gleam@string:join(
        [<<"version_bump "/utf8, "0.1.2"/utf8>>,
            <<""/utf8>>,
            <<"Usage:"/utf8>>,
            <<"  version_bump [--cwd <path>] [--dry-run]   Run the release pipeline"/utf8>>,
            <<"  version_bump --version                    Print the version and exit"/utf8>>,
            <<"  version_bump --help                       Print this help and exit"/utf8>>,
            <<""/utf8>>,
            <<"Flags:"/utf8>>,
            <<"  --cwd <path>   Run against the project at <path> (default: .)"/utf8>>,
            <<"  --dry-run      Compute the next release without tagging or publishing"/utf8>>],
        <<"\n"/utf8>>
    ).

-file("src/version_bump.gleam", 142).
?DOC(" Report an error to the log and exit non-zero.\n").
-spec fail(version_bump@error:release_error()) -> nil.
fail(Err) ->
    version_bump@logging:error(version_bump@error:to_string(Err)),
    version_bump_ffi:halt(1).

-file("src/version_bump.gleam", 133).
?DOC(" Print a human-readable summary of a successful (or no-op) run.\n").
-spec print_summary(version_bump@engine:summary()) -> nil.
print_summary(Summary) ->
    case {erlang:element(2, Summary), erlang:element(3, Summary)} of
        {true, {some, V}} ->
            version_bump@logging:success(
                <<"Published release "/utf8, V/binary>>
            );

        {false, {some, V@1}} ->
            version_bump@logging:info(
                <<"Dry-run: next release would be "/utf8, V@1/binary>>
            );

        {_, none} ->
            version_bump@logging:info(<<"No release published"/utf8>>)
    end.

-file("src/version_bump.gleam", 125).
?DOC(
    " Apply the `--dry-run` flag, only ever turning dry-run on (the flag is an\n"
    " override, never a way to force a real release when config disables it). Pure,\n"
    " so it is unit-tested directly.\n"
).
-spec apply_dry_run(version_bump@config:config(), boolean()) -> version_bump@config:config().
apply_dry_run(Config, Dry_run) ->
    case Dry_run of
        true ->
            {config,
                erlang:element(2, Config),
                erlang:element(3, Config),
                erlang:element(4, Config),
                erlang:element(5, Config),
                true,
                erlang:element(7, Config),
                erlang:element(8, Config)};

        false ->
            Config
    end.

-file("src/version_bump.gleam", 104).
?DOC(" Load config, apply the `--dry-run` override, run the pipeline, and report.\n").
-spec run_release(binary(), boolean()) -> nil.
run_release(Cwd, Dry_run) ->
    Env = envoy_ffi:all(),
    case version_bump@config:load(Cwd) of
        {error, Err} ->
            fail(Err);

        {ok, Loaded} ->
            Config = apply_dry_run(Loaded, Dry_run),
            version_bump_task_ffi:run(
                version_bump@engine:run(Config, Cwd, Env),
                fun(Result) -> case Result of
                        {ok, Summary} ->
                            print_summary(Summary);

                        {error, Err@1} ->
                            fail(Err@1)
                    end end
            )
    end.

-file("src/version_bump.gleam", 78).
?DOC(" Walk the release flags, threading the working directory and dry-run state.\n").
-spec accumulate_release(list(binary()), binary(), boolean()) -> {ok, command()} |
    {error, binary()}.
accumulate_release(Args, Cwd, Dry_run) ->
    case Args of
        [] ->
            {ok, {release, Cwd, Dry_run}};

        [<<"--dry-run"/utf8>> | Rest] ->
            accumulate_release(Rest, Cwd, true);

        [<<"--cwd"/utf8>>] ->
            {error, <<"--cwd requires a path argument"/utf8>>};

        [<<"--cwd"/utf8>>, Value | Rest@1] ->
            case gleam_stdlib:string_starts_with(Value, <<"-"/utf8>>) of
                true ->
                    {error, <<"--cwd requires a path argument"/utf8>>};

                false ->
                    accumulate_release(Rest@1, Value, Dry_run)
            end;

        [Arg | Rest@2] ->
            case gleam@string:split_once(Arg, <<"="/utf8>>) of
                {ok, {<<"--cwd"/utf8>>, <<""/utf8>>}} ->
                    {error, <<"--cwd requires a path argument"/utf8>>};

                {ok, {<<"--cwd"/utf8>>, Value@1}} ->
                    accumulate_release(Rest@2, Value@1, Dry_run);

                _ ->
                    {error, <<"Unknown flag: "/utf8, Arg/binary>>}
            end
    end.

-file("src/version_bump.gleam", 73).
?DOC(
    " Parse the flags accepted by the default (release) command: `--dry-run` and\n"
    " `--cwd <path>` (the `--cwd=<path>` form is also accepted). Unknown flags are\n"
    " rejected so the user gets clear feedback rather than a silent no-op.\n"
).
-spec parse_release_args(list(binary())) -> {ok, command()} | {error, binary()}.
parse_release_args(Args) ->
    accumulate_release(Args, <<"."/utf8>>, false).

-file("src/version_bump.gleam", 59).
?DOC(
    " Classify the raw arguments into a `Command`. Unknown flags are rejected so\n"
    " the user gets clear feedback rather than a silent no-op. Pure, so it is\n"
    " unit-tested directly.\n"
).
-spec parse_args(list(binary())) -> {ok, command()} | {error, binary()}.
parse_args(Args) ->
    case gleam@list:contains(Args, <<"--version"/utf8>>) orelse gleam@list:contains(
        Args,
        <<"version"/utf8>>
    ) of
        true ->
            {ok, show_version};

        false ->
            case gleam@list:contains(Args, <<"--help"/utf8>>) orelse gleam@list:contains(
                Args,
                <<"-h"/utf8>>
            ) of
                true ->
                    {ok, show_help};

                false ->
                    parse_release_args(Args)
            end
    end.

-file("src/version_bump.gleam", 40).
-spec main() -> nil.
main() ->
    Args = erlang:element(4, argv:load()),
    case parse_args(Args) of
        {ok, show_version} ->
            gleam_stdlib:println(<<"0.1.2"/utf8>>);

        {ok, show_help} ->
            gleam_stdlib:println(usage());

        {ok, {release, Cwd, Dry_run}} ->
            run_release(Cwd, Dry_run);

        {error, Message} ->
            version_bump@logging:error(Message),
            gleam_stdlib:println(usage()),
            version_bump_ffi:halt(2)
    end.