-module(version_bump@env_ci). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/version_bump/env_ci.gleam"). -export([detect/1]). -export_type([ci_env/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( " CI environment detection from environment variables. This is a small, pure\n" " reimplementation of the parts of the `env-ci` package that the release\n" " pipeline relies on: figuring out whether we're running in CI, which provider,\n" " the branch and commit under test, and whether the build is for a pull/merge\n" " request.\n" "\n" " Everything here is a pure function of an environment dictionary so it can be\n" " unit-tested without touching the real process environment. Callers wire up\n" " the live environment (e.g. via `envoy`) and pass it in.\n" ). -type ci_env() :: {ci_env, boolean(), binary(), gleam@option:option(binary()), gleam@option:option(binary()), boolean()}. -file("src/version_bump/env_ci.gleam", 144). ?DOC(" Look up a key, treating empty/whitespace-only values as absent.\n"). -spec get(gleam@dict:dict(binary(), binary()), binary()) -> gleam@option:option(binary()). get(Env, Key) -> case gleam_stdlib:map_get(Env, Key) of {ok, Value} -> case gleam@string:trim(Value) of <<""/utf8>> -> none; Trimmed -> {some, Trimmed} end; {error, _} -> none end. -file("src/version_bump/env_ci.gleam", 165). ?DOC( " Whether an optional value is a \"truthy\" flag. CI providers conventionally set\n" " these to \"true\" or \"1\", but any present non-empty value counts as enabled.\n" ). -spec is_truthy(gleam@option:option(binary())) -> boolean(). is_truthy(Value) -> case Value of {some, V} -> case string:lowercase(V) of <<"false"/utf8>> -> false; <<"0"/utf8>> -> false; <<"no"/utf8>> -> false; <<"off"/utf8>> -> false; _ -> true end; none -> false end. -file("src/version_bump/env_ci.gleam", 129). ?DOC( " Generic fallback for any CI service that only sets `CI=true`. No branch or\n" " commit information is assumed, and it is never treated as a PR build.\n" ). -spec detect_generic(gleam@dict:dict(binary(), binary())) -> gleam@option:option(ci_env()). detect_generic(Env) -> case is_truthy(get(Env, <<"CI"/utf8>>)) of false -> none; true -> {some, {ci_env, true, <<"generic"/utf8>>, none, none, false}} end. -file("src/version_bump/env_ci.gleam", 180). ?DOC(" Return the first present value from a list of candidates.\n"). -spec first_present(list(gleam@option:option(binary()))) -> gleam@option:option(binary()). first_present(Candidates) -> case Candidates of [] -> none; [{some, Value} | _] -> {some, Value}; [none | Rest] -> first_present(Rest) end. -file("src/version_bump/env_ci.gleam", 156). ?DOC(" Whether an optional value carries a non-empty string.\n"). -spec is_present(gleam@option:option(binary())) -> boolean(). is_present(Value) -> case Value of {some, _} -> true; none -> false end. -file("src/version_bump/env_ci.gleam", 101). ?DOC( " GitLab CI: identified by `GITLAB_CI=true`.\n" "\n" " A merge-request pipeline is detected via `CI_MERGE_REQUEST_ID` /\n" " `CI_MERGE_REQUEST_IID`, in which case the source branch is\n" " `CI_MERGE_REQUEST_SOURCE_BRANCH_NAME`; otherwise the branch is\n" " `CI_COMMIT_REF_NAME`. The commit is `CI_COMMIT_SHA`.\n" ). -spec detect_gitlab(gleam@dict:dict(binary(), binary())) -> gleam@option:option(ci_env()). detect_gitlab(Env) -> case is_truthy(get(Env, <<"GITLAB_CI"/utf8>>)) of false -> none; true -> Is_pr = is_present(get(Env, <<"CI_MERGE_REQUEST_ID"/utf8>>)) orelse is_present( get(Env, <<"CI_MERGE_REQUEST_IID"/utf8>>) ), Branch = case Is_pr of true -> first_present( [get( Env, <<"CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"/utf8>> ), get(Env, <<"CI_COMMIT_REF_NAME"/utf8>>)] ); false -> get(Env, <<"CI_COMMIT_REF_NAME"/utf8>>) end, {some, {ci_env, true, <<"gitlab"/utf8>>, Branch, get(Env, <<"CI_COMMIT_SHA"/utf8>>), Is_pr}} end. -file("src/version_bump/env_ci.gleam", 190). ?DOC( " Strip a leading `refs/heads/` prefix from a git ref, leaving other refs and\n" " plain branch names untouched.\n" ). -spec strip_ref(gleam@option:option(binary())) -> gleam@option:option(binary()). strip_ref(Value) -> case Value of {some, Ref} -> case gleam_stdlib:string_starts_with(Ref, <<"refs/heads/"/utf8>>) of true -> {some, gleam@string:replace( Ref, <<"refs/heads/"/utf8>>, <<""/utf8>> )}; false -> {some, Ref} end; none -> none end. -file("src/version_bump/env_ci.gleam", 66). ?DOC( " GitHub Actions: identified by `GITHUB_ACTIONS=true`.\n" "\n" " On a pull-request build the source branch lives in `GITHUB_HEAD_REF`; on a\n" " push build the branch is in `GITHUB_REF_NAME` (falling back to stripping the\n" " `refs/heads/` prefix off `GITHUB_REF`). The commit is `GITHUB_SHA`, and a PR\n" " build is signalled by `GITHUB_EVENT_NAME` being `pull_request` or\n" " `pull_request_target`.\n" ). -spec detect_github(gleam@dict:dict(binary(), binary())) -> gleam@option:option(ci_env()). detect_github(Env) -> case is_truthy(get(Env, <<"GITHUB_ACTIONS"/utf8>>)) of false -> none; true -> Event = get(Env, <<"GITHUB_EVENT_NAME"/utf8>>), Is_pr = case Event of {some, <<"pull_request"/utf8>>} -> true; {some, <<"pull_request_target"/utf8>>} -> true; _ -> false end, Branch = case Is_pr of true -> first_present([get(Env, <<"GITHUB_HEAD_REF"/utf8>>)]); false -> first_present( [get(Env, <<"GITHUB_REF_NAME"/utf8>>), strip_ref(get(Env, <<"GITHUB_REF"/utf8>>))] ) end, {some, {ci_env, true, <<"github"/utf8>>, Branch, get(Env, <<"GITHUB_SHA"/utf8>>), Is_pr}} end. -file("src/version_bump/env_ci.gleam", 37). ?DOC( " Detect the CI environment from the given environment variables.\n" "\n" " Providers are checked from most-specific to least-specific so that a generic\n" " `CI=true` only wins when no known provider matched. Returns a `CiEnv` with\n" " `is_ci: False` and an empty provider when nothing CI-like is present.\n" ). -spec detect(gleam@dict:dict(binary(), binary())) -> ci_env(). detect(Env) -> case detect_github(Env) of {some, Result} -> Result; none -> case detect_gitlab(Env) of {some, Result@1} -> Result@1; none -> case detect_generic(Env) of {some, Result@2} -> Result@2; none -> {ci_env, false, <<""/utf8>>, none, none, false} end end end.