char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
51266f9
main
1mod add; 2mod bundle_cli; 3mod check; 4mod cli; 5mod commands; 6mod env_file; 7mod fragments; 8mod lsp; 9mod progress; 10mod repl; 11mod snapshots; 12mod task; 13mod test; 14 15use std:: io:: IsTerminal ; 16use std:: path:: PathBuf ; 17use std:: process:: ExitCode ; 18use std:: rc:: Rc ; 19 20use avril_core:: ModuleLoader ; 21use avril_runtime:: modules:: ModuleGraph ; 22use avril_runtime:: stdlib; 23use std:: sync:: Arc ; 24 25type Result < T > = std:: result:: Result < T , Box < dyn std:: error:: Error >>; 26 27fn main () ->ExitCode { 28// A watched task re-executes avril as its process-group anchor. 29if std:: env:: args_os () 30. nth ( 1 ) 31. is_some_and ( |arg| arg == avril_subprocess:: ANCHOR_ARG ) 32{ 33 avril_subprocess:: anchor (); 34} 35let result =( || ->Result < ExitCode > { 36// Snapshot inherited signal dispositions before the supervisor, 37// listener feeds, or signal owners install anything. 38# [ cfg ( any ( target_os = "linux" , target_os = "macos" ))] 39 avril_subprocess:: signals:: snapshot_originals (); 40let mut args = std:: env:: args_os () 41. skip ( 1 ) 42. map ( |arg|{ 43 arg. into_string () 44. map_err ( |_|"Arguments must be valid UTF-8" ) 45}) 46. collect ::< std:: result:: Result < Vec < _ >, _ >>() ?; 47// A named task owns everything after its name, including --help. 48// The name may be preceded by `task`'s own --watch flag. 49if args. first (). is_some_and ( |arg| arg =="task" ) { 50let rest =& args[ 1 ..]; 51let name =if rest. first (). is_some_and ( |arg| arg =="--watch" ) { 52 rest. get ( 1 ) 53} else { 54 rest. first () 55}; 56if name. is_some_and ( |arg| !arg. starts_with ( '-' )) { 57return task:: run ( task:: parse ( rest) ?); 58} 59} 60if let Some ( help) = cli:: help ( & args) ?{ 61println! ( "{help}" ); 62return Ok ( ExitCode :: SUCCESS ); 63} 64if args. first (). is_some_and ( |arg| arg =="lsp" ) { 65 lsp:: run ( lsp:: parse ( & args[ 1 ..]) ?) ?; 66return Ok ( ExitCode :: SUCCESS ); 67} 68if args. first (). is_some_and ( |arg| arg =="check" ) { 69return check:: run ( check:: parse ( & args[ 1 ..]) ?); 70} 71if args. first (). is_some_and ( |arg| arg =="add" ) { 72return add:: run ( add:: parse ( & args[ 1 ..]) ?). map ( |() |ExitCode :: SUCCESS ); 73} 74if args. first (). is_some_and ( |arg| arg =="task" ) { 75return task:: run ( task:: parse ( & args[ 1 ..]) ?); 76} 77let script = args. split_off ( cli:: script_args_start ( & args)); 78let cwd = fragments:: working_dir () ?; 79let tail = |args : & [ String ] | args[ 1 ..]. to_vec (); 80match args. first (). map ( String :: as_str) { 81Some ( "repl" ) =>{ 82let parsed = commands:: EntryArgs :: parse_repl ( tail ( & args), script, cwd) ?; 83run_command ( parsed, Mode :: Repl ) 84} 85Some ( "run" ) =>{ 86let parsed = commands:: EntryArgs :: parse_run ( tail ( & args), script, cwd) ?; 87run_command ( parsed, Mode :: Run ) 88} 89Some ( "serve" ) =>{ 90let parsed = commands:: ServeArgs :: parse ( tail ( & args), script, cwd) ?; 91run_command ( parsed. common , Mode :: Serve ( parsed. listener )) 92} 93Some ( "test" ) =>{ 94let mut parsed = test:: parse ( tail ( & args), script, cwd) ?; 95 test:: run ( & mut parsed). map ( |() |ExitCode :: SUCCESS ) 96} 97Some ( "cache" ) =>{ 98inspect_command ( commands:: parse_cache ( tail ( & args), cwd) ?, After :: Cache ) 99} 100Some ( "info" ) =>inspect_command ( commands:: parse_info ( tail ( & args), cwd) ?, After :: Info ), 101Some ( "update" ) =>{ 102let parsed = commands:: UpdateArgs :: parse ( tail ( & args), cwd) ?; 103let after =if parsed. prune { 104After :: Update 105} else { 106After :: Cache 107}; 108inspect_command ( parsed. entry , after) 109} 110Some ( "vendor" ) =>{ 111let parsed = commands:: VendorArgs :: parse ( tail ( & args), cwd) ?; 112inspect_command ( parsed. entry , After :: Vendor ( parsed. output )) 113} 114Some ( "bundle" ) =>bundle_command ( bundle_cli:: parse ( tail ( & args), cwd) ?), 115None =>{ 116let parsed = commands:: EntryArgs :: parse_repl ( args, script, cwd) ?; 117run_command ( parsed, Mode :: Repl ) 118} 119Some ( arg) if arg. starts_with ( '-' ) =>{ 120let parsed = commands:: EntryArgs :: parse_repl ( args, script, cwd) ?; 121run_command ( parsed, Mode :: Repl ) 122} 123Some ( arg) =>Err ( format! ( 124"Unknown command: {arg}\nRun a module with `avril run <module>`" 125) 126. into ()), 127} 128})(); 129 130match result{ 131Ok ( code) => code, 132Err ( error) =>{ 133eprintln! ( "{error}" ); 134ExitCode :: FAILURE 135} 136} 137} 138 139enum Mode { 140Run , 141Serve ( avril_http:: Address ), 142Repl , 143} 144 145enum After { 146Cache , 147Update , 148Info , 149Vendor ( Option < PathBuf >), 150} 151 152fn open_graph ( 153authority : avril_runtime:: authority:: SourceAuthority , 154packages : avril_runtime:: registry:: Options , 155entry : & fragments:: Entry , 156prepare : bool , 157progress : Option < avril_runtime:: registry:: ProgressSink >, 158) ->Result <( avril_host:: Host , Rc < ModuleGraph >)> { 159// Declared before the JS runtime so it outlives the extensions' pending host work. 160let host = avril_host:: Host :: new () ?; 161let graph = fragments:: prepare_graph ( 162 host. handle (), 163 authority, 164 packages, 165& entry. module , 166& entry. includes , 167 prepare, 168 progress, 169) ?; 170Ok (( host, graph)) 171} 172 173fn bundle_command ( args : bundle_cli:: BundleArgs ) ->Result < ExitCode > { 174let ( authority, _root, packages) = args. entry . project ( false ) ?; 175// Declared before the JS runtime so it outlives the extensions' pending host work. 176let host = avril_host:: Host :: new () ?; 177let graph =ModuleGraph :: open_browser ( host. handle (), authority, packages) ?; 178 bundle_cli:: run ( graph, & args. entries , & args. output , & args. bundle ) ?; 179Ok ( ExitCode :: SUCCESS ) 180} 181 182fn inspect_command ( entry : fragments:: Entry , after : After ) ->Result < ExitCode > { 183let name = entry. module . clone (); 184let ( authority, root, packages) = entry. project ( false ) ?; 185let reporter =if matches! ( after, After :: Cache ) { 186 progress:: Reporter :: terminal () 187} else { 188None 189}; 190let sink = reporter 191. as_ref () 192. map ( |reporter| reporter. sink ( packages. jsr_registry . clone ())); 193let ( _host, graph) =open_graph ( authority, packages, & entry, true , sink) ?; 194 graph. set_automatic_types ( entry. automatic_types ); 195 graph. set_typescript_version ( avril_lsp:: BACKEND_VERSION ) ?; 196if matches! ( after, After :: Cache ) { 197// Existing corruption takes precedence over expanding the graph with type packages. 198 graph. audit_npm () ?; 199} 200if matches! ( after, After :: Cache |After :: Update |After :: Vendor ( _)) { 201 futures:: executor:: block_on ( graph. prepare_types ( std:: slice:: from_ref ( & name))) ?; 202for ( include, from) in & entry. includes { 203let referrer =match from{ 204Some ( from) =>{ 205 futures:: executor:: block_on ( graph. prepare ( from. clone (), name. clone ())) ? 206} 207None => name. clone (), 208}; 209 futures:: executor:: block_on ( graph. prepare_types_for_request ( include, & referrer)) ?; 210} 211} 212match after{ 213After :: Cache => graph. audit_npm () ?, 214After :: Update =>{ 215 graph. store (). prune () ?; 216} 217After :: Info =>{ 218println! ( "{}" , serde_json:: to_string_pretty ( & graph. info ( & name) ?) ?) 219} 220After :: Vendor ( output) => graph 221. store () 222. vendor ( & output. unwrap_or_else ( || root. join ( "vendor" ))) ?, 223} 224Ok ( ExitCode :: SUCCESS ) 225} 226 227fn run_command ( parsed : commands:: EntryArgs , mode : Mode ) ->Result < ExitCode > { 228let commands:: EntryArgs { entry, script} = parsed; 229let repl =matches! ( mode, Mode :: Repl ); 230let name = entry. module . clone (); 231let ( authority, root, packages) = entry. project ( repl) ?; 232let anchor = entry 233. path 234. to_str () 235. ok_or ( "Module paths must be valid UTF-8" ) ?; 236// A REPL belongs to its project's config wherever it was started 237// from, so its entry component is fixed rather than the cwd-bound 238// synthetic module path; without a config it keys by that root. 239let ( storage_config, storage_entry) =if repl{ 240match ModuleGraph :: project_config_for_repl ( & authority, anchor) ?{ 241Some ( config) =>( config, "repl" ), 242None =>( root. clone (), name. as_str ()), 243} 244} else { 245( root. join ( "avril.conf" ), name. as_str ()) 246}; 247let storage = 248 avril_webstorage:: Persistent :: new ( & packages. cache , & storage_config, storage_entry); 249let reporter =if matches! ( mode, Mode :: Run ) ||( repl && std:: io:: stdin (). is_terminal ()) { 250 progress:: Reporter :: terminal () 251} else { 252None 253}; 254if repl &&let Some ( reporter) =& reporter{ 255 reporter. scope ( false ); 256} 257let sink = reporter 258. as_ref () 259. map ( |reporter| reporter. sink ( packages. jsr_registry . clone ())); 260let ( host, graph) =open_graph ( authority, packages, & entry, !repl, sink) ?; 261let listener =match & mode{ 262Mode :: Serve ( address) =>Some ( address. clone ()), 263 _ =>None , 264}; 265let caps = entry. caps ; 266let environment = entry. environment ; 267let stdio =if repl{ 268 avril_ext_stdio:: Stdio :: new_for_repl () ? 269} else { 270 avril_ext_stdio:: Stdio :: new () ? 271}; 272 graph. set_entry ( & name); 273let mut runtime = avril_runtime:: assembly:: runtime ( 274 graph. clone (), 275 avril_runtime:: assembly:: Settings { 276snapshot : Some ( snapshots:: embedded ()), 277 storage, 278 caps, 279 environment, 280args : script, 281store : graph. store (). clone (), 282runtime : host. handle (), 283broker : Arc :: default (), 284process_signal : match mode{ 285Mode :: Repl => avril_ext_process:: SignalMode :: Repl , 286Mode :: Serve ( _) => avril_ext_process:: SignalMode :: Serve , 287Mode :: Run => avril_ext_process:: SignalMode :: Run , 288}, 289}, 290 stdio. clone (), 291 listener, 292None , 293) ?; 294if repl{ 295let result = repl:: run ( 296& mut runtime, 297& name, 298& stdio, 299& host. handle (), 300 reporter. as_ref (), 301); 302let flushed = stdio. flush (); 303 result?; 304 flushed?; 305return Ok ( ExitCode :: SUCCESS ); 306} 307let result =if matches! ( mode, Mode :: Serve ( _)) { 308 runtime. execute_module_with_bootstrap ( & name, "ext:http/main.js" , "startCLI" ) 309} else { 310 runtime. execute_module ( & name) 311}; 312let flushed = stdio. flush (); 313 result?; 314 flushed?; 315Ok ( ExitCode :: SUCCESS ) 316}