-module(plushie@dev_server). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/plushie/dev_server.gleam"). -export([start/1, start_supervised/1]). -export_type([dev_message/0, dev_state/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( " Dev server: file watcher and recompiler for live reload.\n" "\n" " Watches source directories for `.gleam` file changes, runs\n" " `gleam build`, detects changed BEAM modules, reloads them, and\n" " tells the runtime to re-render. The UI updates without losing\n" " application state.\n" "\n" " Started automatically when `dev: True` is set in StartOpts.\n" " Requires the `file_system` Erlang package.\n" ). -opaque dev_message() :: recompile | {raw_file_event, gleam@dynamic:dynamic_()}. -type dev_state() :: {dev_state, gleam@erlang@process:subject(plushie@runtime:runtime_message()), gleam@dynamic:dynamic_(), boolean(), list({gleam@dynamic:dynamic_(), gleam@dynamic:dynamic_()}), gleam@erlang@process:subject(dev_message())}. -file("src/plushie/dev_server.gleam", 188). ?DOC( " Check if a path is inside a top-level build output directory.\n" " Matches \"/build/dev/\" or \"build/dev/\" but not \"/my_build/\".\n" ). -spec is_build_output_dir(binary()) -> boolean(). is_build_output_dir(Path) -> gleam_stdlib:contains_string(Path, <<"/build/dev/"/utf8>>) orelse gleam_stdlib:string_starts_with( Path, <<"build/dev/"/utf8>> ). -file("src/plushie/dev_server.gleam", 179). ?DOC( " Check if a path is a Gleam source file worth watching.\n" " Excludes _build/ and build/ only when they appear as directory\n" " boundaries, not as substrings of other directory names.\n" ). -spec is_gleam_file(binary()) -> boolean(). is_gleam_file(Path) -> ((gleam_stdlib:string_ends_with(Path, <<".gleam"/utf8>>) andalso not gleam_stdlib:contains_string( Path, <<"/_build/"/utf8>> )) andalso not gleam_stdlib:string_starts_with(Path, <<"_build/"/utf8>>)) andalso not is_build_output_dir(Path). -file("src/plushie/dev_server.gleam", 112). -spec handle_message(dev_state(), dev_message()) -> gleam@otp@actor:next(dev_state(), dev_message()). handle_message(State, Msg) -> case Msg of {raw_file_event, Raw} -> case plushie_dev_server_ffi:extract_file_path(Raw) of {ok, Path} -> case is_gleam_file(Path) of true -> case erlang:element(4, State) of true -> gleam@otp@actor:continue(State); false -> gleam@erlang@process:send_after( erlang:element(6, State), 100, recompile ), gleam@otp@actor:continue( {dev_state, erlang:element(2, State), erlang:element(3, State), true, erlang:element(5, State), erlang:element(6, State)} ) end; false -> gleam@otp@actor:continue(State) end; {error, _} -> gleam@otp@actor:continue(State) end; recompile -> State@1 = {dev_state, erlang:element(2, State), erlang:element(3, State), false, erlang:element(5, State), erlang:element(6, State)}, gleam_stdlib:println(<<"plushie dev: recompiling..."/utf8>>), Output = plushie_ffi:gleam_build(), case gleam_stdlib:contains_string(Output, <<"error"/utf8>>) of true -> gleam_stdlib:println(<<"plushie dev: build failed:"/utf8>>), gleam_stdlib:println(Output), gleam@otp@actor:continue(State@1); false -> New_mtimes = plushie_ffi:list_beam_files( <<"build/dev/erlang"/utf8>> ), Changed = plushie_dev_server_ffi:find_changed_modules( erlang:element(5, State@1), New_mtimes ), case Changed of [] -> gleam_stdlib:println( <<"plushie dev: no modules changed"/utf8>> ), gleam@otp@actor:continue( {dev_state, erlang:element(2, State@1), erlang:element(3, State@1), erlang:element(4, State@1), New_mtimes, erlang:element(6, State@1)} ); Modules -> plushie_ffi:reload_modules(Modules), gleam@erlang@process:send( erlang:element(2, State@1), force_rerender ), gleam_stdlib:println( <<"plushie dev: reload complete"/utf8>> ), gleam@otp@actor:continue( {dev_state, erlang:element(2, State@1), erlang:element(3, State@1), erlang:element(4, State@1), New_mtimes, erlang:element(6, State@1)} ) end end end. -file("src/plushie/dev_server.gleam", 74). -spec start_actor( gleam@erlang@process:subject(plushie@runtime:runtime_message()) ) -> {ok, gleam@otp@actor:started(gleam@erlang@process:subject(dev_message()))} | {error, gleam@otp@actor:start_error()}. start_actor(Runtime) -> Initial_mtimes = plushie_ffi:list_beam_files(<<"build/dev/erlang"/utf8>>), Watcher = plushie_ffi:start_file_watcher([<<"src/"/utf8>>]), plushie_ffi:file_watcher_subscribe(Watcher), _pipe@5 = gleam@otp@actor:new_with_initialiser( 5000, fun(Self) -> Initial_state = {dev_state, Runtime, Watcher, false, Initial_mtimes, Self}, Selector = begin _pipe = gleam_erlang_ffi:new_selector(), _pipe@1 = gleam@erlang@process:select(_pipe, Self), gleam@erlang@process:select_other( _pipe@1, fun(Msg) -> {raw_file_event, Msg} end ) end, _pipe@2 = gleam@otp@actor:initialised(Initial_state), _pipe@3 = gleam@otp@actor:selecting(_pipe@2, Selector), _pipe@4 = gleam@otp@actor:returning(_pipe@3, Self), {ok, _pipe@4} end ), _pipe@6 = gleam@otp@actor:on_message(_pipe@5, fun handle_message/2), gleam@otp@actor:start(_pipe@6). -file("src/plushie/dev_server.gleam", 51). ?DOC(" Start the dev server actor, watching src/ for changes.\n"). -spec start(gleam@erlang@process:subject(plushie@runtime:runtime_message())) -> nil. start(Runtime) -> _ = start_actor(Runtime), gleam_stdlib:println( <<"plushie dev: watching "/utf8, (gleam@string:join([<<"src/"/utf8>>], <<", "/utf8>>))/binary>> ), nil. -file("src/plushie/dev_server.gleam", 60). ?DOC( " Start the dev server under a supervisor.\n" "\n" " Returns `Started` for use as a supervisor child spec.\n" ). -spec start_supervised( gleam@erlang@process:subject(plushie@runtime:runtime_message()) ) -> {ok, gleam@otp@actor:started(gleam@erlang@process:subject(dev_message()))} | {error, gleam@otp@actor:start_error()}. start_supervised(Runtime) -> case start_actor(Runtime) of {ok, Started} -> gleam_stdlib:println( <<"plushie dev: watching "/utf8, (gleam@string:join([<<"src/"/utf8>>], <<", "/utf8>>))/binary>> ), {ok, Started}; {error, Err} -> {error, Err} end.