-module(version_bump@plugins@npm). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/version_bump/plugins/npm.gleam"). -export([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 npm publish plugin (plugin name \"npm\").\n" "\n" " Mirrors `@semantic-release/npm`. It manages a `package.json` and the npm\n" " registry across three hooks:\n" "\n" " - verify_conditions: there must be a `package.json` in `context.cwd` and an\n" " `NPM_TOKEN` available (checked in `context.env`, then the process env).\n" " - prepare: rewrite the `\"version\"` field of `package.json` to the next\n" " release version, preserving the rest of the file verbatim.\n" " - publish: run `npm publish` in `context.cwd`, returning a `Release`.\n" "\n" " The only PURE function is `set_version`, which performs the `package.json`\n" " version rewrite as a string transformation so it can be unit-tested without\n" " any IO. It is exported for that reason.\n" ). -file("src/version_bump/plugins/npm.gleam", 237). ?DOC(" Shell out to `npm publish`, mapping a non-zero exit to a `PluginError`.\n"). -spec run_npm_publish(binary()) -> {ok, nil} | {error, version_bump@error:release_error()}. run_npm_publish(Cwd) -> case shellout:command(<<"npm"/utf8>>, [<<"publish"/utf8>>], Cwd, []) of {ok, _} -> {ok, nil}; {error, {Code, Message}} -> {error, {plugin_error, <<"npm"/utf8>>, <<<<<<"`npm publish` failed (exit "/utf8, (erlang:integer_to_binary(Code))/binary>>/binary, "): "/utf8>>/binary, (gleam@string:trim(Message))/binary>>}} end. -file("src/version_bump/plugins/npm.gleam", 264). ?DOC( " Extract the next release from the context, failing when the pipeline has not\n" " determined one (which should never happen by the time `prepare`/`publish`\n" " run, but is 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, <<"npm"/utf8>>, <<"no next release determined"/utf8>>}} end. -file("src/version_bump/plugins/npm.gleam", 216). ?DOC( " Run `npm publish` in `context.cwd` and report the resulting release. `npm publish`\n" " is synchronous (a subprocess), so the result is wrapped in an already-resolved\n" " task to satisfy the asynchronous `publish` contract.\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_npm_publish(erlang:element(2, Context)), fun(_) -> {ok, {some, {release, <<"npm"/utf8>>, none, erlang:element(2, Next), erlang:element(4, Next), erlang:element(6, Next), <<"npm"/utf8>>}}} end ) end ) end ). -file("src/version_bump/plugins/npm.gleam", 135). ?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, <<"npm"/utf8>>, <<<<<<"could not write "/utf8, Path/binary>>/binary, ": "/utf8>>/binary, (simplifile:describe_error(Err))/binary>>} end ). -file("src/version_bump/plugins/npm.gleam", 191). ?DOC( " Replace only the first occurrence of `needle` in `haystack` with\n" " `replacement`. Used so that rewriting the top-level `version` never also\n" " rewrites an identical `\"version\": \"x\"` pair nested deeper in the document.\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/npm.gleam", 205). ?DOC( " Escape a string for safe inclusion inside a JSON string literal. A semantic\n" " version contains only `[0-9A-Za-z.+-]`, so in practice nothing needs\n" " escaping, but this keeps `set_version` correct for arbitrary input.\n" ). -spec escape_json_string(binary()) -> binary(). escape_json_string(Value) -> _pipe = Value, _pipe@1 = gleam@string:replace(_pipe, <<"\\"/utf8>>, <<"\\\\"/utf8>>), gleam@string:replace(_pipe@1, <<"\""/utf8>>, <<"\\\""/utf8>>). -file("src/version_bump/plugins/npm.gleam", 182). ?DOC( " Compile the regexp matching a JSON `\"version\": \"\"` pair.\n" "\n" " Submatch 1 captures everything up to and including the opening quote of the\n" " value (the key, colon, whitespace, and opening `\"`); submatch 2 captures the\n" " existing value. Rebuilding `prefix <> new_value <> \"\\\"\"` preserves the\n" " original key spacing while swapping only the value.\n" ). -spec version_regexp() -> {ok, gleam@regexp:regexp()} | {error, binary()}. version_regexp() -> Pattern = <<"(\"version\"\\s*:\\s*\")((?:\\\\.|[^\"\\\\])*)\""/utf8>>, _pipe = gleam@regexp:from_string(Pattern), gleam@result:map_error( _pipe, fun(Err) -> <<"invalid version regexp: "/utf8, (erlang:element(2, Err))/binary>> end ). -file("src/version_bump/plugins/npm.gleam", 155). ?DOC( " Replace the top-level `\"version\"` field in a `package.json` document with\n" " `version`, leaving the rest of the document byte-for-byte intact.\n" "\n" " PURE: a string transformation, no IO. Returns a `PluginError` when no\n" " `\"version\"` field is present so callers can surface a clear message rather\n" " than silently producing a package.json without a version.\n" "\n" " The match targets the first `\"version\": \"...\"` pair, which in a well-formed\n" " `package.json` is the top-level package version. Only the quoted value is\n" " rewritten; surrounding whitespace and formatting are preserved.\n" ). -spec set_version(binary(), binary()) -> {ok, binary()} | {error, version_bump@error:release_error()}. set_version(Package_json, Version) -> case version_regexp() of {error, Message} -> {error, {plugin_error, <<"npm"/utf8>>, Message}}; {ok, Re} -> case gleam@regexp:scan(Re, Package_json) of [{match, Matched, [{some, Prefix}, _]} | _] -> Replacement = <<<>/binary, "\""/utf8>>, {ok, replace_first(Package_json, Matched, Replacement)}; _ -> {error, {plugin_error, <<"npm"/utf8>>, <<"no \"version\" field found in package.json"/utf8>>}} end end. -file("src/version_bump/plugins/npm.gleam", 124). ?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, <<"npm"/utf8>>, <<<<<<"could not read "/utf8, Path/binary>>/binary, ": "/utf8>>/binary, (simplifile:describe_error(Err))/binary>>} end ). -file("src/version_bump/plugins/npm.gleam", 254). ?DOC(" The path to `package.json` within the working directory.\n"). -spec package_json_path(binary()) -> binary(). package_json_path(Cwd) -> case gleam_stdlib:string_ends_with(Cwd, <<"/"/utf8>>) of true -> <>; false -> <> end. -file("src/version_bump/plugins/npm.gleam", 115). ?DOC( " Rewrite `package.json`'s `version` to the next release version and write it\n" " back to disk.\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 = package_json_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/npm.gleam", 88). ?DOC( " Resolve the npm auth token from `context.env`, falling back to the live process\n" " environment. Empty/whitespace-only values are treated as absent.\n" ). -spec npm_token(version_bump@context:context()) -> gleam@option:option(binary()). npm_token(Context) -> From_ctx = case gleam_stdlib:map_get( erlang:element(3, Context), <<"NPM_TOKEN"/utf8>> ) of {ok, Value} -> {some, Value}; {error, _} -> none end, Token = case From_ctx of {some, Value@1} -> {some, Value@1}; none -> case envoy_ffi:get(<<"NPM_TOKEN"/utf8>>) of {ok, Value@2} -> {some, Value@2}; {error, _} -> none end end, case Token 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/npm.gleam", 75). ?DOC( " Verify that an `NPM_TOKEN` is available, checking the context environment\n" " first and falling back to the live process environment.\n" ). -spec ensure_npm_token(version_bump@context:context()) -> {ok, nil} | {error, version_bump@error:release_error()}. ensure_npm_token(Context) -> case npm_token(Context) of {some, _} -> {ok, nil}; none -> {error, {plugin_error, <<"npm"/utf8>>, <<"NPM_TOKEN environment variable is not set"/utf8>>}} end. -file("src/version_bump/plugins/npm.gleam", 64). ?DOC(" Verify that `package.json` is present in the working directory.\n"). -spec ensure_package_json_exists(binary()) -> {ok, nil} | {error, version_bump@error:release_error()}. ensure_package_json_exists(Cwd) -> Path = package_json_path(Cwd), case simplifile_erl:is_file(Path) of {ok, true} -> {ok, nil}; {ok, false} -> {error, {plugin_error, <<"npm"/utf8>>, <<"no package.json found at "/utf8, Path/binary>>}}; {error, _} -> {error, {plugin_error, <<"npm"/utf8>>, <<"no package.json found at "/utf8, Path/binary>>}} end. -file("src/version_bump/plugins/npm.gleam", 50). ?DOC(" Ensure a `package.json` exists in `context.cwd` and an npm auth token is present.\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'( ensure_package_json_exists(erlang:element(2, Context)), fun(_) -> case erlang:element(12, Context) of true -> {ok, nil}; false -> ensure_npm_token(Context) end end ). -file("src/version_bump/plugins/npm.gleam", 38). ?DOC( " Build the npm plugin: implements `verify_conditions`, `prepare`, and\n" " `publish`.\n" ). -spec plugin() -> version_bump@plugin:plugin(). plugin() -> _record = version_bump@plugin:new(<<"npm"/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)}.