-module(version_bump@plugins@hex). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/version_bump/plugins/hex.gleam"). -export([package_name/1, set_version/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 Hex / Gleam publish plugin (plugin name \"hex\").\n" "\n" " The Gleam analogue of `@semantic-release/npm`. It publishes the package to\n" " the Hex package repository with `gleam publish`. Two things differ from npm:\n" " the version lives in `gleam.toml` (not `package.json`), and Hex has no\n" " dist-tag / channel concept, so `add_channel` is intentionally NOT implemented\n" " (a prerelease is published as an ordinary semver prerelease and Hex surfaces\n" " it as such).\n" "\n" " - verify_conditions: a `gleam.toml` exists in `context.cwd` carrying the\n" " `description` and `licences` fields that `gleam publish` requires, and a\n" " `HEXPM_API_KEY` is available (skipped on a dry run, since a dry run never\n" " publishes — matching the npm/github plugins here).\n" " - prepare: rewrite the top-level `version` field of `gleam.toml`.\n" " - publish: run `gleam publish --yes` and report the hex.pm release URL.\n" "\n" " `set_version` and `package_name` are PURE string functions, exported so the\n" " version rewrite and URL construction can be unit-tested without any IO.\n" ). -file("src/version_bump/plugins/hex.gleam", 271). ?DOC(" Compile a multi-line regexp so `^` anchors to each line start.\n"). -spec compile_ml(binary()) -> {ok, gleam@regexp:regexp()} | {error, binary()}. compile_ml(Pattern) -> _pipe = gleam@regexp:compile(Pattern, {options, false, true}), gleam@result:map_error( _pipe, fun(Err) -> <<"invalid regexp: "/utf8, (erlang:element(2, Err))/binary>> end ). -file("src/version_bump/plugins/hex.gleam", 256). ?DOC(" Read the package `name` from a `gleam.toml` document. PURE.\n"). -spec package_name(binary()) -> {ok, binary()} | {error, version_bump@error:release_error()}. package_name(Gleam_toml) -> case compile_ml(<<"^[ \t]*name[ \t]*=[ \t]*\"([^\"]*)\""/utf8>>) of {error, Message} -> {error, {plugin_error, <<"hex"/utf8>>, Message}}; {ok, Re} -> case gleam@regexp:scan(Re, Gleam_toml) of [{match, _, [{some, Name}]} | _] -> {ok, Name}; _ -> {error, {plugin_error, <<"hex"/utf8>>, <<"no `name` field found in gleam.toml"/utf8>>}} end end. -file("src/version_bump/plugins/hex.gleam", 292). ?DOC(" The path to `gleam.toml` within the working directory.\n"). -spec gleam_toml_path(binary()) -> binary(). gleam_toml_path(Cwd) -> case gleam_stdlib:string_ends_with(Cwd, <<"/"/utf8>>) of true -> <>; false -> <> end. -file("src/version_bump/plugins/hex.gleam", 300). ?DOC(" Read a file, mapping any failure to a `PluginError`.\n"). -spec read_file(binary()) -> {ok, binary()} | {error, version_bump@error:release_error()}. read_file(Path) -> _pipe = simplifile:read(Path), gleam@result:map_error( _pipe, fun(Err) -> {plugin_error, <<"hex"/utf8>>, <<<<<<"could not read "/utf8, Path/binary>>/binary, ": "/utf8>>/binary, (simplifile:describe_error(Err))/binary>>} end ). -file("src/version_bump/plugins/hex.gleam", 219). ?DOC( " Best-effort hex.pm URL for the published release; `None` if the package name\n" " cannot be read (publishing already succeeded, so this is not fatal).\n" ). -spec release_url(binary(), binary()) -> gleam@option:option(binary()). release_url(Cwd, Version) -> case read_file(gleam_toml_path(Cwd)) of {ok, Contents} -> case package_name(Contents) of {ok, Name} -> {some, <<<<<<"https://hex.pm/packages/"/utf8, Name/binary>>/binary, "/"/utf8>>/binary, Version/binary>>}; {error, _} -> none end; {error, _} -> none end. -file("src/version_bump/plugins/hex.gleam", 188). ?DOC( " Shell out to `gleam publish --yes`, mapping a non-zero exit to a\n" " `PluginError`.\n" ). -spec run_gleam_publish(binary()) -> {ok, nil} | {error, version_bump@error:release_error()}. run_gleam_publish(Cwd) -> Publish = <<"echo 'I am not using semantic versioning' | gleam publish --yes"/utf8>>, case shellout:command( <<"sh"/utf8>>, [<<"-c"/utf8>>, Publish], Cwd, [let_be_stdout, let_be_stderr] ) of {ok, _} -> {ok, nil}; {error, {Code, _}} -> {error, {plugin_error, <<"hex"/utf8>>, <<<<"`gleam publish --yes` failed (exit "/utf8, (erlang:integer_to_binary(Code))/binary>>/binary, ")"/utf8>>}} end. -file("src/version_bump/plugins/hex.gleam", 323). ?DOC( " Extract the next release from the context, failing when the pipeline has not\n" " determined one (handled rather than asserted).\n" ). -spec require_next_release(version_bump@context:context()) -> {ok, version_bump@release:next_release()} | {error, version_bump@error:release_error()}. require_next_release(Context) -> case erlang:element(9, Context) of {some, Next} -> {ok, Next}; none -> {error, {plugin_error, <<"hex"/utf8>>, <<"no next release determined"/utf8>>}} end. -file("src/version_bump/plugins/hex.gleam", 166). ?DOC( " Run `gleam publish --yes` in `context.cwd` and report the resulting release. The\n" " `HEXPM_API_KEY` in the environment is inherited by the subprocess.\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(_, Context) -> version_bump_task_ffi:resolve( begin gleam@result:'try'( require_next_release(Context), fun(Next) -> gleam@result:'try'( run_gleam_publish(erlang:element(2, Context)), fun(_) -> {ok, {some, {release, <<"hex"/utf8>>, release_url( erlang:element(2, Context), erlang:element(2, Next) ), erlang:element(2, Next), erlang:element(4, Next), erlang:element(6, Next), <<"hex"/utf8>>}}} end ) end ) end ). -file("src/version_bump/plugins/hex.gleam", 311). ?DOC(" Write a file, mapping any failure to a `PluginError`.\n"). -spec write_file(binary(), binary()) -> {ok, nil} | {error, version_bump@error:release_error()}. write_file(Path, Contents) -> _pipe = simplifile:write(Path, Contents), gleam@result:map_error( _pipe, fun(Err) -> {plugin_error, <<"hex"/utf8>>, <<<<<<"could not write "/utf8, Path/binary>>/binary, ": "/utf8>>/binary, (simplifile:describe_error(Err))/binary>>} end ). -file("src/version_bump/plugins/hex.gleam", 280). ?DOC(" Replace only the first occurrence of `needle` in `haystack`.\n"). -spec replace_first(binary(), binary(), binary()) -> binary(). replace_first(Haystack, Needle, Replacement) -> case gleam@string:split_once(Haystack, Needle) of {ok, {Before, After}} -> <<<>/binary, After/binary>>; {error, _} -> Haystack end. -file("src/version_bump/plugins/hex.gleam", 234). ?DOC( " Replace the top-level `version = \"...\"` value in a `gleam.toml` document with\n" " `version`, leaving the rest of the file intact. PURE.\n" ). -spec set_version(binary(), binary()) -> {ok, binary()} | {error, version_bump@error:release_error()}. set_version(Gleam_toml, Version) -> case compile_ml(<<"(^[ \t]*version[ \t]*=[ \t]*\")([^\"]*)\""/utf8>>) of {error, Message} -> {error, {plugin_error, <<"hex"/utf8>>, Message}}; {ok, Re} -> case gleam@regexp:scan(Re, Gleam_toml) of [{match, Matched, [{some, Prefix}, _]} | _] -> Replacement = <<<>/binary, "\""/utf8>>, {ok, replace_first(Gleam_toml, Matched, Replacement)}; _ -> {error, {plugin_error, <<"hex"/utf8>>, <<"no top-level `version` field found in gleam.toml"/utf8>>}} end end. -file("src/version_bump/plugins/hex.gleam", 154). ?DOC(" Rewrite `gleam.toml`'s `version` to the next release version, in place.\n"). -spec prepare(version_bump@config:plugin_spec(), version_bump@context:context()) -> {ok, nil} | {error, version_bump@error:release_error()}. prepare(_, Context) -> gleam@result:'try'( require_next_release(Context), fun(Next) -> Path = gleam_toml_path(erlang:element(2, Context)), gleam@result:'try'( read_file(Path), fun(Contents) -> gleam@result:'try'( set_version(Contents, erlang:element(2, Next)), fun(Updated) -> write_file(Path, Updated) end ) end ) end ). -file("src/version_bump/plugins/hex.gleam", 128). ?DOC( " Resolve the Hex API key from `context.env`, falling back to the live process\n" " environment. Empty/whitespace-only values are treated as absent.\n" ). -spec api_key(version_bump@context:context()) -> gleam@option:option(binary()). api_key(Context) -> From_ctx = case gleam_stdlib:map_get( erlang:element(3, Context), <<"HEXPM_API_KEY"/utf8>> ) of {ok, Value} -> {some, Value}; {error, _} -> none end, Key = case From_ctx of {some, Value@1} -> {some, Value@1}; none -> case envoy_ffi:get(<<"HEXPM_API_KEY"/utf8>>) of {ok, Value@2} -> {some, Value@2}; {error, _} -> none end end, case Key of {some, Value@3} -> case gleam@string:trim(Value@3) of <<""/utf8>> -> none; Trimmed -> {some, Trimmed} end; none -> none end. -file("src/version_bump/plugins/hex.gleam", 114). ?DOC( " Verify that a `HEXPM_API_KEY` is available, checking the context environment\n" " first and falling back to the live process environment.\n" ). -spec ensure_api_key(version_bump@context:context()) -> {ok, nil} | {error, version_bump@error:release_error()}. ensure_api_key(Context) -> case api_key(Context) of {some, _} -> {ok, nil}; none -> {error, {plugin_error, <<"hex"/utf8>>, <<"HEXPM_API_KEY is not set (run `gleam hex authenticate` to create a key, "/utf8, "then expose it as HEXPM_API_KEY in CI)"/utf8>>}} end. -file("src/version_bump/plugins/hex.gleam", 101). ?DOC( " True when `field` appears as an uncommented top-level key. A leading `#`\n" " (comment) prevents a match, so the commented placeholders in a freshly\n" " scaffolded `gleam.toml` are correctly treated as absent.\n" ). -spec field_present(binary(), binary()) -> boolean(). field_present(Contents, Field) -> case compile_ml(<<<<"^[ \t]*"/utf8, Field/binary>>/binary, "[ \t]*="/utf8>>) of {ok, Re} -> case gleam@regexp:scan(Re, Contents) of [] -> false; _ -> true end; {error, _} -> false end. -file("src/version_bump/plugins/hex.gleam", 85). -spec require_field(binary(), binary()) -> {ok, nil} | {error, version_bump@error:release_error()}. require_field(Contents, Field) -> case field_present(Contents, Field) of true -> {ok, nil}; false -> {error, {plugin_error, <<"hex"/utf8>>, <<<<"gleam.toml is missing the `"/utf8, Field/binary>>/binary, "` field, which `gleam publish` requires to publish to Hex"/utf8>>}} end. -file("src/version_bump/plugins/hex.gleam", 80). ?DOC( " `gleam publish` refuses to publish a package without `description` and\n" " `licences`, so verify they are present (and uncommented) up front rather than\n" " failing after the version has already been bumped and tagged.\n" ). -spec ensure_publishable_metadata(binary()) -> {ok, nil} | {error, version_bump@error:release_error()}. ensure_publishable_metadata(Contents) -> gleam@result:'try'( require_field(Contents, <<"description"/utf8>>), fun(_) -> require_field(Contents, <<"licences"/utf8>>) end ). -file("src/version_bump/plugins/hex.gleam", 68). ?DOC(" Read `gleam.toml`, mapping a missing/unreadable file to a clear error.\n"). -spec read_gleam_toml(binary()) -> {ok, binary()} | {error, version_bump@error:release_error()}. read_gleam_toml(Cwd) -> Path = gleam_toml_path(Cwd), case simplifile:read(Path) of {ok, Contents} -> {ok, Contents}; {error, _} -> {error, {plugin_error, <<"hex"/utf8>>, <<"no gleam.toml found at "/utf8, Path/binary>>}} end. -file("src/version_bump/plugins/hex.gleam", 55). ?DOC( " Ensure `gleam.toml` exists with the metadata `gleam publish` requires, and\n" " that a Hex API key is available (except on a dry run).\n" ). -spec verify_conditions( version_bump@config:plugin_spec(), version_bump@context:context() ) -> {ok, nil} | {error, version_bump@error:release_error()}. verify_conditions(_, Context) -> gleam@result:'try'( read_gleam_toml(erlang:element(2, Context)), fun(Contents) -> gleam@result:'try'( ensure_publishable_metadata(Contents), fun(_) -> case erlang:element(12, Context) of true -> {ok, nil}; false -> ensure_api_key(Context) end end ) end ). -file("src/version_bump/plugins/hex.gleam", 42). ?DOC( " Build the Hex plugin: implements `verify_conditions`, `prepare`, and\n" " `publish`. There is deliberately no `add_channel` — Hex has no dist-tags.\n" ). -spec plugin() -> version_bump@plugin:plugin(). plugin() -> _record = version_bump@plugin:new(<<"hex"/utf8>>), {plugin, erlang:element(2, _record), {some, fun verify_conditions/2}, erlang:element(4, _record), erlang:element(5, _record), erlang:element(6, _record), erlang:element(7, _record), {some, fun prepare/2}, {some, fun publish/2}, erlang:element(10, _record), erlang:element(11, _record)}.