From c5ec04a0c7317919dec0ac4b671cb6c4f2dd54e9 Mon Sep 17 00:00:00 2001 From: Daniel Widgren Date: Wed, 11 Mar 2026 21:40:51 +0100 Subject: [PATCH 1/5] feat: replace thoas with Erlang/OTP json module Remove the thoas dependency and use the built-in json module from OTP 27+ for all JSON encoding/decoding. This removes the json_lib configuration option since OTP json is always available. Key changes: - Replace all thoas:encode/1 calls with json:encode/1 - Replace thoas:decode/1 (returns {ok, Term}) with json:decode/1 (returns Term directly, throws on error) in nova_request_plugin - Convert charlist values to binaries in nova_error_controller since OTP json encodes charlists as JSON arrays, not strings - Add ensure_json_safe/1 helper for CrashInfo values - Update all tests for json module API differences - Remove json_lib from configuration docs Co-Authored-By: Claude Opus 4.6 --- guides/configuration.md | 11 +---- rebar.config | 3 +- rebar.lock | 21 ++++------ src/controllers/nova_error_controller.erl | 27 +++++++----- src/nova.app.src | 3 +- src/nova_basic_handler.erl | 6 +-- src/nova_jsonlogger.erl | 51 +++++++++++------------ src/plugins/nova_request_plugin.erl | 12 +++--- test/nova_error_controller_tests.erl | 2 +- test/nova_test_helper.erl | 3 +- 10 files changed, 63 insertions(+), 76 deletions(-) diff --git a/guides/configuration.md b/guides/configuration.md index 6b14eeed..d360f2c4 100644 --- a/guides/configuration.md +++ b/guides/configuration.md @@ -41,18 +41,9 @@ These parameters can be specified in your *main* application (Eg the one you've | Key | Description | Value | |-----|-------------|-------| -| `json_lib` | JSON lib to use. Read more in the subsection *Configure json lib* | `atom()` | | `watchers` | Watchers are external programs that will run together with Nova. Watchers are defined as list of tuples where the tuples is in format `{Command, ArgumentList}` (Like `[{my_app, "npm", ["run", "watch"], #{workdir => "priv/assets/js/my-app"}}]`) | `[{string(), string()}] | [{atom(), string(), map()}] | [{atom(), string(), list(), map()}]` | - - -### Configure json_lib - -One can configure which json library to use for encoding/decoding json structures. The module defined for this should expose two different functions: - -`encode(Structure) -> binary() | iolist()` - -`decode(JsonString) -> {ok, Structure}` +> **Note:** Nova uses the Erlang/OTP `json` module for JSON encoding and decoding. The `json_lib` configuration option has been removed. ## Handling errors in Nova diff --git a/rebar.config b/rebar.config index 62a190b6..f5884f1c 100644 --- a/rebar.config +++ b/rebar.config @@ -14,8 +14,7 @@ {cowboy, "2.13.0"}, {erlydtl, "0.14.0"}, {jhn_stdlib, "5.4.0"}, - {routing_tree, "1.0.11"}, - {thoas, "1.2.1"} + {routing_tree, "1.0.11"} ]}. {profiles, [ diff --git a/rebar.lock b/rebar.lock index e38da47a..d5a32473 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,26 +1,23 @@ {"1.2.0", [{<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.13.0">>},0}, - {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.13.0">>},1}, + {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.16.0">>},1}, {<<"erlydtl">>,{pkg,<<"erlydtl">>,<<"0.14.0">>},0}, {<<"jhn_stdlib">>,{pkg,<<"jhn_stdlib">>,<<"5.4.0">>},0}, - {<<"ranch">>,{pkg,<<"ranch">>,<<"2.1.0">>},1}, - {<<"routing_tree">>,{pkg,<<"routing_tree">>,<<"1.0.11">>},0}, - {<<"thoas">>,{pkg,<<"thoas">>,<<"1.2.1">>},0}]}. + {<<"ranch">>,{pkg,<<"ranch">>,<<"2.2.0">>},1}, + {<<"routing_tree">>,{pkg,<<"routing_tree">>,<<"1.0.11">>},0}]}. [ {pkg_hash,[ {<<"cowboy">>, <<"09D770DD5F6A22CC60C071F432CD7CB87776164527F205C5A6B0F24FF6B38990">>}, - {<<"cowlib">>, <<"DB8F7505D8332D98EF50A3EF34B34C1AFDDEC7506E4EE4DD4A3A266285D282CA">>}, + {<<"cowlib">>, <<"54592074EBBBB92EE4746C8A8846E5605052F29309D3A873468D76CDF932076F">>}, {<<"erlydtl">>, <<"964B2DC84F8C17ACFAA69C59BA129EF26AC45D2BA898C3C6AD9B5BDC8BA13CED">>}, {<<"jhn_stdlib">>, <<"FAC6F19B35351278F1CB156E23A5B2A6047A9DD5AB1FD9E1189A7918006DF7ED">>}, - {<<"ranch">>, <<"2261F9ED9574DCFCC444106B9F6DA155E6E540B2F82BA3D42B339B93673B72A3">>}, - {<<"routing_tree">>, <<"72ACEF2095F0EC804F7AFD07EF781DDE5009425A1CA0A28F0706B1DB334A4812">>}, - {<<"thoas">>, <<"19A25F31177A17E74004D4840F66D791D4298C5738790FA2CC73731EB911F195">>}]}, + {<<"ranch">>, <<"25528F82BC8D7C6152C57666CA99EC716510FE0925CB188172F41CE93117B1B0">>}, + {<<"routing_tree">>, <<"72ACEF2095F0EC804F7AFD07EF781DDE5009425A1CA0A28F0706B1DB334A4812">>}]}, {pkg_hash_ext,[ {<<"cowboy">>, <<"E724D3A70995025D654C1992C7B11DBFEA95205C047D86FF9BF1CDA92DDC5614">>}, - {<<"cowlib">>, <<"E1E1284DC3FC030A64B1AD0D8382AE7E99DA46C3246B815318A4B848873800A4">>}, + {<<"cowlib">>, <<"7F478D80D66B747344F0EA7708C187645CFCC08B11AA424632F78E25BF05DB51">>}, {<<"erlydtl">>, <<"D80EC044CD8F58809C19D29AC5605BE09E955040911B644505E31E9DD8143431">>}, {<<"jhn_stdlib">>, <<"7EABD1B01D2DEFF495BF7C5CA1DBA4D3FA0B84DC3AF03CA85F31D52EBB03C6FC">>}, - {<<"ranch">>, <<"244EE3FA2A6175270D8E1FC59024FD9DBC76294A321057DE8F803B1479E76916">>}, - {<<"routing_tree">>, <<"85982C7AC502892C5179CD2A591331003BACD2D2A71723640BA7D23F45408E6E">>}, - {<<"thoas">>, <<"E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A">>}]} + {<<"ranch">>, <<"FA0B99A1780C80218A4197A59EA8D3BDAE32FBFF7E88527D7D8A4787EFF4F8E7">>}, + {<<"routing_tree">>, <<"85982C7AC502892C5179CD2A591331003BACD2D2A71723640BA7D23F45408E6E">>}]} ]. diff --git a/src/controllers/nova_error_controller.erl b/src/controllers/nova_error_controller.erl index f38598de..81297d9b 100644 --- a/src/controllers/nova_error_controller.erl +++ b/src/controllers/nova_error_controller.erl @@ -23,8 +23,7 @@ not_found(Req) -> lists:member(<<"text/html">>, AcceptList)} of {true, _} -> %% Render a json response - JsonLib = nova:get_env(json_lib, thoas), - Json = JsonLib:encode(#{message => "Resource not found"}), + Json = json:encode(#{message => <<"Resource not found">>}), {status, 404, #{<<"content-type">> => <<"application/json">>}, Json}; {_, true} -> %% Just assume HTML @@ -47,8 +46,7 @@ server_error(#{crash_info := #{status_code := StatusCode} = CrashInfo} = Req) -> true -> case cowboy_req:header(<<"accept">>, Req) of <<"application/json">> -> - JsonLib = nova:get_env(json_lib, thoas), - Json = JsonLib:encode(Variables), + Json = json:encode(ensure_json_safe(Variables)), {status, StatusCode, #{<<"content-type">> => <<"application/json">>}, Json}; <<"text/html">> -> {ok, Body} = nova_error_dtl:render(Variables), @@ -61,10 +59,10 @@ server_error(#{crash_info := #{status_code := StatusCode} = CrashInfo} = Req) -> end; server_error(#{crash_info := #{class := Class, reason := Reason}} = Req) -> Stacktrace = maps:get(stacktrace, Req, []), - Variables = #{status => "Internal Server Error", - title => "500 Internal Server Error", - message => "Something internal crashed. Please take a look!", - extra_msg => io_lib:format("Class: ~p
Reason: ~p", [Class, Reason]), + Variables = #{status => <<"Internal Server Error">>, + title => <<"500 Internal Server Error">>, + message => <<"Something internal crashed. Please take a look!">>, + extra_msg => iolist_to_binary(io_lib:format("Class: ~p
Reason: ~p", [Class, Reason])), stacktrace => format_stacktrace(Stacktrace)}, case nova:get_environment() of @@ -72,8 +70,7 @@ server_error(#{crash_info := #{class := Class, reason := Reason}} = Req) -> %% We do show a proper error response case cowboy_req:header(<<"accept">>, Req) of <<"application/json">> -> - JsonLib = nova:get_env(json_lib, thoas), - Json = JsonLib:encode(Variables), + Json = json:encode(Variables), {status, 500, #{<<"content-type">> => <<"application/json">>}, Json}; <<"text/html">> -> {ok, Body} = nova_error_dtl:render(Variables), @@ -123,6 +120,16 @@ format_arity(Arity, _) when is_function(Arity)-> format_arity(Arity, _) -> Arity. +ensure_json_safe(Map) when is_map(Map) -> + maps:map(fun(_K, V) -> ensure_json_safe(V) end, Map); +ensure_json_safe(List) when is_list(List) -> + case io_lib:printable_unicode_list(List) of + true -> unicode:characters_to_binary(List); + false -> [ensure_json_safe(E) || E <- List] + end; +ensure_json_safe(Value) -> + Value. + -ifdef(TEST). -compile(export_all). -endif. diff --git a/src/nova.app.src b/src/nova.app.src index 2d026633..458aa28b 100644 --- a/src/nova.app.src +++ b/src/nova.app.src @@ -13,8 +13,7 @@ compiler, erlydtl, jhn_stdlib, - routing_tree, - thoas + routing_tree ]}, {env,[]}, {modules,[nova]}, diff --git a/src/nova_basic_handler.erl b/src/nova_basic_handler.erl index ad440958..d5a840a4 100644 --- a/src/nova_basic_handler.erl +++ b/src/nova_basic_handler.erl @@ -43,8 +43,7 @@ handle_json({json, StatusCode, Headers, Req0, JSON}, Callback, _Req) -> handle_json({json, StatusCode, Headers, JSON}, Callback, Req0); handle_json({json, StatusCode, Headers, JSON}, _Callback, Req) -> - JsonLib = nova:get_env(json_lib, thoas), - EncodedJSON = JsonLib:encode(JSON), + EncodedJSON = json:encode(JSON), Headers0 = maps:merge(#{<<"content-type">> => <<"application/json">>}, Headers), Req0 = cowboy_req:set_resp_headers(Headers0, Req), Req1 = cowboy_req:set_resp_body(EncodedJSON, Req0), @@ -154,11 +153,10 @@ handle_status({status, Status, ExtraHeaders, JSON, Req0}, Callback, _Req) -> handle_status({status, Status, ExtraHeaders, JSON}, Callback, Req0); handle_status({status, Status, ExtraHeaders, JSON}, _Callback, Req) when is_map(JSON) -> %% We do not need to render a status page since we just return a JSON structure - JsonLib = nova:get_env(json_lib, thoas), Headers0 = maps:merge(#{<<"content-type">> => <<"application/json">>}, ExtraHeaders), Req0 = cowboy_req:set_resp_headers(Headers0, Req), Req1 = Req0#{resp_status_code => Status}, - JSONStr = JsonLib:encode(JSON), + JSONStr = json:encode(JSON), Req2 = cowboy_req:set_resp_body(JSONStr, Req1), {ok, Req2}; handle_status({status, Status, ExtraHeaders, Body}, _Callback, Req) -> diff --git a/src/nova_jsonlogger.erl b/src/nova_jsonlogger.erl index c8433312..a4d65599 100644 --- a/src/nova_jsonlogger.erl +++ b/src/nova_jsonlogger.erl @@ -85,8 +85,7 @@ merge_meta(Msg, Meta0, Config) -> maps:merge(Msg, Meta2). encode(Data, Config) -> - JsonLib = nova:get_env(json_lib, thoas), - Json = JsonLib:encode(Data), + Json = json:encode(Data), case new_line(Config) of true -> [Json, new_line_type(Config)]; false -> Json @@ -162,7 +161,7 @@ meta_with(Meta, _ConfigNotPresent) -> -include_lib("eunit/include/eunit.hrl"). -define(assertJSONEqual(Expected, Actual), - ?assertEqual(thoas:decode(Expected), thoas:decode(Actual)) + ?assertEqual(json:decode(Expected), json:decode(iolist_to_binary(Actual))) ). format_test() -> @@ -260,20 +259,20 @@ meta_without_test() -> meta => #{secret => xyz} }, ?assertEqual( - {ok, #{ + #{ <<"answer">> => 42, <<"level">> => <<"info">>, <<"secret">> => <<"xyz">> - }}, - thoas:decode(format(Error, #{})) + }, + json:decode(iolist_to_binary(format(Error, #{}))) ), Config2 = #{meta_without => [secret]}, ?assertEqual( - {ok, #{ + #{ <<"answer">> => 42, <<"level">> => <<"info">> - }}, - thoas:decode(format(Error, Config2)) + }, + json:decode(iolist_to_binary(format(Error, Config2))) ), ok. @@ -284,36 +283,34 @@ meta_with_test() -> meta => #{secret => xyz} }, ?assertEqual( - {ok, #{ + #{ <<"answer">> => 42, <<"level">> => <<"info">>, <<"secret">> => <<"xyz">> - }}, - thoas:decode(format(Error, #{})) + }, + json:decode(iolist_to_binary(format(Error, #{}))) ), Config2 = #{meta_with => [level]}, ?assertEqual( - {ok, #{ + #{ <<"answer">> => 42, <<"level">> => <<"info">> - }}, - thoas:decode(format(Error, Config2)) + }, + json:decode(iolist_to_binary(format(Error, Config2))) ), ok. newline_test() -> ConfigDefault = #{new_line => true}, - ?assertEqual( - [<<"{\"level\":\"alert\",\"text\":\"derp\"}">>, <<"\n">>], - format(#{level => alert, msg => {string, "derp"}, meta => #{}}, ConfigDefault) - ), - ConfigCRLF = #{ - new_line_type => crlf, - new_line => true - }, - ?assertEqual( - [<<"{\"level\":\"alert\",\"text\":\"derp\"}">>, <<"\r\n">>], - format(#{level => alert, msg => {string, "derp"}, meta => #{}}, ConfigCRLF) - ). + [JsonDefault, NlDefault] = format(#{level => alert, msg => {string, "derp"}, meta => #{}}, ConfigDefault), + ?assertEqual(#{<<"level">> => <<"alert">>, <<"text">> => <<"derp">>}, + json:decode(iolist_to_binary(JsonDefault))), + ?assertEqual(<<"\n">>, NlDefault), + + ConfigCRLF = #{new_line_type => crlf, new_line => true}, + [JsonCRLF, NlCRLF] = format(#{level => alert, msg => {string, "derp"}, meta => #{}}, ConfigCRLF), + ?assertEqual(#{<<"level">> => <<"alert">>, <<"text">> => <<"derp">>}, + json:decode(iolist_to_binary(JsonCRLF))), + ?assertEqual(<<"\r\n">>, NlCRLF). -endif. \ No newline at end of file diff --git a/src/plugins/nova_request_plugin.erl b/src/plugins/nova_request_plugin.erl index 7421d68d..fa4384c1 100644 --- a/src/plugins/nova_request_plugin.erl +++ b/src/plugins/nova_request_plugin.erl @@ -78,15 +78,15 @@ modulate_state(Req = #{headers := #{<<"content-type">> := <<"application/json", {stop, Req400, State}; modulate_state(Req = #{headers := #{<<"content-type">> := <<"application/json", _/binary>>}, body := Body}, [{decode_json_body, true}|Tl], State) -> %% Decode the data - JsonLib = nova:get_env(json_lib, thoas), - case JsonLib:decode(Body) of - {ok, JSON} -> - modulate_state(Req#{json => JSON}, Tl, State); - Error -> + try json:decode(Body) of + JSON -> + modulate_state(Req#{json => JSON}, Tl, State) + catch + error:Reason -> Req400 = cowboy_req:reply(400, Req), logger:warning(#{status_code => 400, msg => "Failed to decode json.", - error => Error}), + error => Reason}), {stop, Req400, State} end; modulate_state(#{headers := #{<<"content-type">> := <<"application/x-www-form-urlencoded", _/binary>>}, body := Body} = Req, diff --git a/test/nova_error_controller_tests.erl b/test/nova_error_controller_tests.erl index 159b0660..fc735b99 100644 --- a/test/nova_error_controller_tests.erl +++ b/test/nova_error_controller_tests.erl @@ -69,7 +69,7 @@ not_found_json_accept_test_() -> Req1 = nova_test_helper:with_header(<<"accept">>, <<"application/json">>, Req), {status, 404, Headers, Body} = nova_error_controller:not_found(Req1), ?assertEqual(<<"application/json">>, maps:get(<<"content-type">>, Headers)), - ?assert(is_binary(Body)) + ?assert(is_list(Body) orelse is_binary(Body)) end}. %% When no accept header, defaults to JSON diff --git a/test/nova_test_helper.erl b/test/nova_test_helper.erl index bf7ca42f..ea36c473 100644 --- a/test/nova_test_helper.erl +++ b/test/nova_test_helper.erl @@ -32,7 +32,7 @@ with_header(Name, Value, Req = #{headers := Headers}) -> -spec with_json_body(map() | binary(), map()) -> map(). with_json_body(JSON, Req) when is_map(JSON) -> - Body = thoas:encode(JSON), + Body = iolist_to_binary(json:encode(JSON)), with_json_body(Body, Req); with_json_body(Body, Req) when is_binary(Body) -> Req1 = with_content_type(<<"application/json">>, Req), @@ -61,7 +61,6 @@ setup_nova_env() -> Prev = application:get_env(nova, bootstrap_application), application:set_env(nova, bootstrap_application, nova), application:set_env(nova, environment, dev), - application:set_env(nova, json_lib, thoas), Prev. -spec cleanup_nova_env(undefined | {ok, atom()}) -> ok. From e455445b80eab06c69ab1e0e5cc9197bf4ec8258 Mon Sep 17 00:00:00 2001 From: Daniel Widgren Date: Wed, 11 Mar 2026 21:46:51 +0100 Subject: [PATCH 2/5] chore: drop OTP 26 from CI matrix OTP 27+ is now required since the json module is used for JSON encoding/decoding. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/erlang.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/erlang.yml b/.github/workflows/erlang.yml index 3d5bab7f..cf17c10b 100644 --- a/.github/workflows/erlang.yml +++ b/.github/workflows/erlang.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - otp: ['26.1', '27.1', '28.0'] + otp: ['27.1', '28.0'] rebar3: ['3.25.0'] steps: - uses: actions/checkout@v6 @@ -39,7 +39,7 @@ jobs: strategy: fail-fast: false matrix: - otp: ['26.1', '27.1', '28.0'] + otp: ['27.1', '28.0'] rebar3: ['3.25.0'] steps: - uses: actions/checkout@v6 @@ -65,7 +65,7 @@ jobs: strategy: fail-fast: false matrix: - otp: ['26.1', '27.1', '28.0'] + otp: ['27.1', '28.0'] rebar3: ['3.25.0'] steps: - uses: actions/checkout@v6 @@ -91,7 +91,7 @@ jobs: strategy: fail-fast: false matrix: - otp: ['26.1', '27.1', '28.0'] + otp: ['27.1', '28.0'] rebar3: ['3.25.0'] steps: - uses: actions/checkout@v6 From 6dc7f70f58c8c6ae0b45cafcad3233977c6078fb Mon Sep 17 00:00:00 2001 From: Daniel Widgren Date: Wed, 11 Mar 2026 21:47:37 +0100 Subject: [PATCH 3/5] chore: bump rebar3 to 3.26.0 in CI matrix Co-Authored-By: Claude Opus 4.6 --- .github/workflows/erlang.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/erlang.yml b/.github/workflows/erlang.yml index cf17c10b..fffd8da7 100644 --- a/.github/workflows/erlang.yml +++ b/.github/workflows/erlang.yml @@ -13,7 +13,7 @@ jobs: fail-fast: false matrix: otp: ['27.1', '28.0'] - rebar3: ['3.25.0'] + rebar3: ['3.26.0'] steps: - uses: actions/checkout@v6 - uses: erlef/setup-beam@v1 @@ -40,7 +40,7 @@ jobs: fail-fast: false matrix: otp: ['27.1', '28.0'] - rebar3: ['3.25.0'] + rebar3: ['3.26.0'] steps: - uses: actions/checkout@v6 - uses: erlef/setup-beam@v1 @@ -66,7 +66,7 @@ jobs: fail-fast: false matrix: otp: ['27.1', '28.0'] - rebar3: ['3.25.0'] + rebar3: ['3.26.0'] steps: - uses: actions/checkout@v6 - uses: erlef/setup-beam@v1 @@ -92,7 +92,7 @@ jobs: fail-fast: false matrix: otp: ['27.1', '28.0'] - rebar3: ['3.25.0'] + rebar3: ['3.26.0'] steps: - uses: actions/checkout@v6 - uses: erlef/setup-beam@v1 From b04e980e7e7df7e7611f9ac0180de6606d36af04 Mon Sep 17 00:00:00 2001 From: Daniel Widgren Date: Wed, 11 Mar 2026 21:49:23 +0100 Subject: [PATCH 4/5] chore: update CI action versions and OTP matrix - Bump actions/cache from v4 to v5 - Bump actions/checkout from v4 to v6 in release workflow - Bump OTP matrix from 27.1/28.0 to 27.3/28.3 Co-Authored-By: Claude Opus 4.6 --- .github/workflows/erlang.yml | 16 ++++++++-------- .github/workflows/release.yml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/erlang.yml b/.github/workflows/erlang.yml index fffd8da7..a9e65e71 100644 --- a/.github/workflows/erlang.yml +++ b/.github/workflows/erlang.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - otp: ['27.1', '28.0'] + otp: ['27.3', '28.3'] rebar3: ['3.26.0'] steps: - uses: actions/checkout@v6 @@ -22,7 +22,7 @@ jobs: rebar3-version: ${{matrix.rebar3}} version-type: strict - name: Cache rebar3 deps and build - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | ~/.cache/rebar3 @@ -39,7 +39,7 @@ jobs: strategy: fail-fast: false matrix: - otp: ['27.1', '28.0'] + otp: ['27.3', '28.3'] rebar3: ['3.26.0'] steps: - uses: actions/checkout@v6 @@ -49,7 +49,7 @@ jobs: rebar3-version: ${{matrix.rebar3}} version-type: strict - name: Cache rebar3 deps and build - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | ~/.cache/rebar3 @@ -65,7 +65,7 @@ jobs: strategy: fail-fast: false matrix: - otp: ['27.1', '28.0'] + otp: ['27.3', '28.3'] rebar3: ['3.26.0'] steps: - uses: actions/checkout@v6 @@ -75,7 +75,7 @@ jobs: rebar3-version: ${{matrix.rebar3}} version-type: strict - name: Cache rebar3 deps and build - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | ~/.cache/rebar3 @@ -91,7 +91,7 @@ jobs: strategy: fail-fast: false matrix: - otp: ['27.1', '28.0'] + otp: ['27.3', '28.3'] rebar3: ['3.26.0'] steps: - uses: actions/checkout@v6 @@ -101,7 +101,7 @@ jobs: rebar3-version: ${{matrix.rebar3}} version-type: strict - name: Cache rebar3 deps and build - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | ~/.cache/rebar3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f6f3ddd..fe7f1c0c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,7 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 From 6c4e79e5e5730acd57fda532998f182fb339075f Mon Sep 17 00:00:00 2001 From: Daniel Widgren Date: Wed, 11 Mar 2026 22:14:33 +0100 Subject: [PATCH 5/5] chore: opt into Node.js 24 for GitHub Actions erlef/setup-beam@v1 still uses node20 which is deprecated June 2026. Set FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 to opt in early. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/erlang.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/erlang.yml b/.github/workflows/erlang.yml index a9e65e71..d00548a6 100644 --- a/.github/workflows/erlang.yml +++ b/.github/workflows/erlang.yml @@ -5,6 +5,9 @@ on: branches: [master] pull_request: +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: build: runs-on: ubuntu-24.04