char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
38d7f6c
main
1import { 2avril , pledge , env , process , runtime , inspect , 3serve , serveFile , upgradeWebSocket , connect , listen , listenDatagram , 4Conn , Listener , DatagramConn , FileHandle , FsWatcher , SeekMode , errors , 5open , create , stat , lstat , readDir , watchFs , mkdir , remove , rename , copyFile , 6symlink , readLink , realPath , chmod , chown , lchown , utime , lutime , link , truncate , 7ChildProcess , ProcessError , OutputLimitError , ProcessTimeoutError , ProcessAbortError , 8run , runSync , spawn , addSignalListener , removeSignalListener , stdin , stdout , stderr , 9type TcpAddr , type UnixAddr , type UdpAddr , type StreamAddr , type Addr , 10type ServeInfo , type ServeHandler , type ServeModule , type HttpServer , 11type ProcessReason , type ProcessStatus , type ProcessOutput , type ProcessPromise , 12type ProcessOptions , type SpawnOptions , type RunSyncOptions , type ProcessTerminal , 13type ProcessStdio , type ExtraProcessStdio , type FileInfo , type DirEntry , type FsEvent , 14type FsEventKind , type WatchOptions , type OpenOptions , type Pledge , type GrantRestriction , 15} from "avril:std" ; 16import * as sync from "avril:std/sync" ; 17// @ts-expect-error Facade support types are private. 18import type { ClassStatics } from "avril:std" ; 19 20const brand :symbol = avril ; 21const branded :{ readonly [ avril ] :true } = { [ avril ] :true }; 22const connectionName :string = Conn . name ; 23const listenerArity :number = Listener . length ; 24const reflected :Function = ChildProcess ; 25const prototype :Conn < StreamAddr > = Conn . prototype ; 26// @ts-expect-error Class prototype properties cannot be replaced. 27Conn . prototype = prototype ; 28const restriction :GrantRestriction < string | URL > = { allow :[ new URL ( "file:///tmp/" )] }; 29const narrowed :Pledge = { read :restriction , net :false }; 30pledge ( narrowed ); 31// @ts-expect-error Authority categories use the capability vocabulary. 32pledge ({ filesystem :false }); 33const variable :string | undefined = env . get ( "HOME" ); 34const snapshot :Record < string , string > = env . toObject (); 35const pid :number = process . pid ; 36const args :readonly string [] = process . args ; 37const os :"linux" | "macos" | "windows" = runtime . os ; 38const arch :"x86_64" | "aarch64" = runtime . arch ; 39const version :string = runtime . version ; 40const v8 :string = runtime . v8 ; 41const cwd :string = process . cwd (); 42const averages :[ number , number , number ] = process . loadavg (); 43const formatted :string = inspect ( snapshot , { depth :null , colors :false }); 44// @ts-expect-error Frozen script arguments are not mutable. 45process . args . push ( "extra" ); 46// @ts-expect-error Runtime metadata is readonly. 47runtime . os = "linux" ; 48 49const tcpServer :HttpServer < TcpAddr > = serve (( request , info ) => { 50const port :number = info . remoteAddr . port ; 51// @ts-expect-error TCP peer addresses have no Unix path. 52info . remoteAddr . path ; 53return new Response ( request . url + port ); 54}); 55serve ({ port :0 , onListen ( addr ) { const port :number = addr . port ; } }, async () => new Response ()); 56const unixServer :HttpServer < UnixAddr > = serve ({ 57path :"/tmp/http.sock" , 58onListen ( addr ) { const path :string | null = addr . path ; }, 59}, ( request , info ) => { 60const path :string | null = info . remoteAddr . path ; 61// @ts-expect-error Unix peer addresses have no TCP port. 62info . remoteAddr . port ; 63return new Response ( path ); 64}); 65// @ts-expect-error A Unix listener cannot also have a TCP port. 66serve ({ path :"/tmp/http.sock" , port :80 }, () => new Response ()); 67// @ts-expect-error A handler must return a Response. 68serve (() => "not a response" ); 69const handler :ServeHandler = ( request , info :ServeInfo ) => new Response ( info . remoteAddr . transport ); 70const module :ServeModule = { fetch :handler }; 71const shutdown :Promise < void > = unixServer . shutdown (); 72const upgraded :{ socket :WebSocket ; response :Response } = upgradeWebSocket ( new Request ( "http://localhost/" )); 73 74const tcp = await connect ({ port :80 }); 75const tcpAddress :TcpAddr = tcp . remoteAddr ; 76const unix = await connect ({ transport :"unix" , path :"/tmp/socket" }); 77const unixAddress :UnixAddr = unix . remoteAddr ; 78const tcpListener = listen ({ port :0 }); 79const accepted :Conn < TcpAddr > = await tcpListener . accept (); 80const unixListener = listen ({ transport :"unix" , path :"/tmp/socket" }); 81const unixAccepted :Conn < UnixAddr > = await unixListener . accept (); 82const tcpConnections :AsyncIterable < Conn < TcpAddr >> = tcpListener ; 83const unixConnections :AsyncIterable < Conn < UnixAddr >> = unixListener ; 84const finishedWrite :Promise < void > = tcp . closeWrite (); 85// @ts-expect-error closeWrite has no result. 86const nullShutdown :Promise < null > = tcp . closeWrite (); 87// @ts-expect-error Unix connections cannot be used as TCP connections. 88const wrongConnection :Conn < TcpAddr > = unix ; 89// @ts-expect-error Stream listeners do not use UDP. 90listen ({ transport :"udp" , port :0 }); 91// @ts-expect-error TCP connect requires a port. 92connect ({ hostname :"localhost" }); 93const datagram = listenDatagram ({ port :0 }); 94const received :[ Uint8Array , UdpAddr ] = await datagram . receive (); 95const packets :AsyncIterable <[ Uint8Array , UdpAddr ]> = datagram ; 96const streamAddress :StreamAddr = unixAddress ; 97const address :Addr = received [ 1 ]; 98 99const options :OpenOptions = { read :true , write :true }; 100const file :FileHandle = await open ( new URL ( "file:///data" ), options ); 101const bytes = new Uint8Array ( new SharedArrayBuffer ( 8 )); 102const count :number | null = await file . read ( bytes ); 103const written :number = await file . writev ([ bytes ] as const ); 104const writtenSync :number = file . writevSync ([ bytes ] as const ); 105const seek :Promise < number > = file . seek ( 0 , SeekMode . Start ); 106const metadata :FileInfo = await file . stat (); 107const entries :AsyncIterable < DirEntry > = readDir ( "." ); 108const watcherOptions :WatchOptions = { recursive :true , signal :new AbortController (). signal }; 109const watcher :FsWatcher = watchFs ([ "." , new URL ( "file:///tmp" )], watcherOptions ); 110const events :AsyncIterable < FsEvent > = watcher ; 111const kind :FsEventKind = "rescan" ; 112const disposable :Disposable = file ; 113const watchDisposable :Disposable = watcher ; 114const response :Promise < Response > = serveFile ( new Request ( "http://localhost/" ), file , { consume :false }); 115// @ts-expect-error Seek modes are a closed set. 116file . seek ( 0 , 3 ); 117// @ts-expect-error Reads require byte views. 118file . read ( new Uint16Array ()); 119// @ts-expect-error writev requires an array, not an arbitrary iterator. 120file . writev ( new Set ([ bytes ])); 121 122const syncFile :FileHandle = sync . open ( "data" ); 123const syncType :sync . FileHandle = file ; 124const syncOptions :sync . OpenOptions = options ; 125const syncMetadata :sync . FileInfo = sync . stat ( "data" ); 126const syncEntries :Iterable < sync . DirEntry > = sync . readDir ( "." ); 127const syncErrors :typeof errors = sync . errors ; 128const syncHandle :typeof FileHandle = sync . FileHandle ; 129const syncSeek :2 = sync . SeekMode . End ; 130const readCount :number | null = sync . stdin . read ( bytes ); 131const writeCount :number = sync . stdout . write ( bytes ); 132const errorCount :number = sync . stderr . write ( bytes ); 133// @ts-expect-error Sync operations do not return promises. 134const notAsync :Promise < FileInfo > = sync . stat ( "data" ); 135// @ts-expect-error Sync paths are strings or URLs. 136sync . open ( 42 ); 137 138const raw :ProcessPromise = run ( "tool" ); 139const text :ProcessPromise < string > = run ( "tool" , { encoding :"utf-8" }); 140const positional :ProcessPromise = run ( "tool" , [ "arg" ], { check :false }); 141const positionalText :ProcessPromise < string > = run ( "tool" , undefined , { encoding :"utf-8" }); 142const rawOutput :ProcessOutput = await raw ; 143const textOutput :ProcessOutput < string > = await text ; 144const outputBytes :Uint8Array | null = rawOutput . stdout ; 145const outputText :string | null = textOutput . stdout ; 146const shortcutBytes :Promise < Uint8Array > = text . bytes (); 147const shortcutText :Promise < string > = raw . text (); 148const json :Promise < unknown > = text . json (); 149const lines :AsyncIterableIterator < string > = text . lines (); 150const syncRaw :ProcessOutput = runSync ( "tool" ); 151const syncText :ProcessOutput < string > = runSync ( "tool" , { encoding :"utf-8" }); 152const syncPositional :ProcessOutput = runSync ( "tool" , [ "arg" ]); 153const syncPositionalText :ProcessOutput < string > = runSync ( "tool" , [ "arg" ], { encoding :"utf-8" }); 154const child :ChildProcess = spawn ( "tool" ); 155const textChild :ChildProcess < string > = spawn ( "tool" , { encoding :"utf-8" }); 156const childPositional :ChildProcess = spawn ( "tool" , [ "arg" ]); 157const textChildPositional :ChildProcess < string > = spawn ( "tool" , undefined , { encoding :"utf-8" }); 158const childOutput :ProcessOutput < string > = await textChild . output (); 159const status :ProcessStatus = await child . status ; 160const reason :ProcessReason | null = status . reason ; 161const terminal :ProcessTerminal | null = child . terminal ; 162const childDisposable :AsyncDisposable = child ; 163const processOptions :ProcessOptions = { stdin :{ handle :file }, input :new ReadableStream < Uint8Array >() }; 164const spawnOptions :SpawnOptions = { terminal :{ columns :80 }, extraStdio :{ 3 :{ pipe :"child-to-parent" } } }; 165const syncRunOptions :RunSyncOptions = { input :bytes }; 166const stdioOption :ProcessStdio = { file :"output" , mode :"append" }; 167const extra :ExtraProcessStdio = { handle :file , direction :"child-to-parent" }; 168// @ts-expect-error Raw output is bytes, not decoded text. 169const wrongText :string | null = rawOutput . stdout ; 170// @ts-expect-error Decoded output is text, not bytes. 171const wrongBytes :Uint8Array | null = textOutput . stderr ; 172// @ts-expect-error Positional args cannot be repeated in options. 173run ( "tool" , [ "arg" ], { args :[ "again" ] }); 174// @ts-expect-error Sync runs do not accept streaming input. 175runSync ( "tool" , { input :new ReadableStream < Uint8Array >() }); 176// @ts-expect-error Sync runs do not accept an AbortSignal. 177runSync ( "tool" , { signal :new AbortController (). signal }); 178// @ts-expect-error Spawn results are not promises. 179const notPromise :Promise < unknown > = child ; 180// @ts-expect-error Native failure reasons have a closed vocabulary. 181const invalidReason :ProcessReason = "other" ; 182 183const failure = new ProcessError ( "failed" , rawOutput , new Error ()); 184const limited = new OutputLimitError ( textOutput ); 185const timedOut = new ProcessTimeoutError ( rawOutput ); 186const aborted = new ProcessAbortError ( null , "cancel" ); 187const parent :ProcessError = limited ; 188const error :Error = failure ; 189const isProcessError :boolean = ProcessError . isError ( failure ); 190const isOutputLimitError :boolean = OutputLimitError . isError ( limited ); 191const isProcessTimeoutError :boolean = ProcessTimeoutError . isError ( timedOut ); 192const isProcessAbortError :boolean = ProcessAbortError . isError ( aborted ); 193class CustomProcessError extends ProcessError {} 194 195declare const unknownValue :unknown ; 196if ( unknownValue instanceof Conn ) { 197const addr :StreamAddr = unknownValue . remoteAddr ; 198// @ts-expect-error instanceof alone cannot establish TCP transport. 199const tcpOnly :Conn < TcpAddr > = unknownValue ; 200} 201if ( unknownValue instanceof Listener ) { 202const conn :Conn < StreamAddr > = await unknownValue . accept (); 203} 204if ( unknownValue instanceof DatagramConn ) { 205const addr :UdpAddr = unknownValue . addr ; 206} 207if ( unknownValue instanceof FileHandle ) { 208const result :FileInfo = await unknownValue . stat (); 209} 210if ( unknownValue instanceof sync . FileHandle ) { 211const handle :FileHandle = unknownValue ; 212} 213if ( unknownValue instanceof FsWatcher ) { 214const iterable :AsyncIterable < FsEvent > = unknownValue ; 215} 216if ( unknownValue instanceof ChildProcess ) { 217const pid :number = unknownValue . pid ; 218} 219if ( unknownValue instanceof ProcessError ) { 220const error :Error = unknownValue ; 221const output :ProcessOutput < Uint8Array | string > | null = unknownValue . result ; 222} 223if ( unknownValue instanceof OutputLimitError ) { 224const parent :ProcessError = unknownValue ; 225const error :Error = unknownValue ; 226} 227if ( unknownValue instanceof ProcessTimeoutError ) { 228const parent :ProcessError = unknownValue ; 229} 230if ( unknownValue instanceof ProcessAbortError ) { 231const parent :ProcessError = unknownValue ; 232} 233// @ts-expect-error Connections cannot be constructed. 234new Conn (); 235// @ts-expect-error Listeners cannot be constructed. 236new Listener (); 237// @ts-expect-error Datagram sockets cannot be constructed. 238new DatagramConn (); 239// @ts-expect-error File handles cannot be constructed. 240new FileHandle (); 241// @ts-expect-error Watchers cannot be constructed. 242new FsWatcher (); 243// @ts-expect-error Child processes must be spawned. 244new ChildProcess (); 245// @ts-expect-error ProcessError's message is required. 246new ProcessError (); 247// @ts-expect-error OutputLimitError requires a result. 248new OutputLimitError (); 249 250const inputStream :ReadableStream < Uint8Array > = stdin ; 251const outputStream :WritableStream < Uint8Array > = stdout ; 252const errorStream :WritableStream < Uint8Array > = stderr ; 253addSignalListener ( "SIGTERM" , () => {}); 254removeSignalListener ( "SIGTERM" , () => {}); 255// @ts-expect-error Signal listeners take callbacks. 256addSignalListener ( "SIGTERM" , 42 ); 257 258// Keep the ordinary filesystem operations represented alongside their sync siblings. 259const created :Promise < FileHandle > = create ( "file" ); 260const stats :Promise < FileInfo >[] = [ stat ( "." ), lstat ( "." )]; 261const changes :Promise < void >[] = [ 262mkdir ( "dir" ), remove ( "dir" ), rename ( "a" , "b" ), copyFile ( "a" , "b" ), 263symlink ( "target" , "link" ), chmod ( "file" , 0o600 ), chown ( "file" , null , null ), 264lchown ( "link" , null , null ), utime ( "file" , new Date (), 0 ), lutime ( "link" , 0 , 0 ), 265link ( "a" , "b" ), truncate ( "file" ), 266]; 267const paths :Promise < string >[] = [ readLink ( "link" ), realPath ( "." )]; 268const syncCreated :FileHandle = sync . create ( "file" ); 269const syncStats :FileInfo [] = [ sync . stat ( "." ), sync . lstat ( "." )]; 270const syncChanges :void [] = [ 271sync . mkdir ( "dir" ), sync . remove ( "dir" ), sync . rename ( "a" , "b" ), sync . copyFile ( "a" , "b" ), 272sync . symlink ( "target" , "link" ), sync . chmod ( "file" , 0o600 ), sync . chown ( "file" , null , null ), 273sync . lchown ( "link" , null , null ), sync . utime ( "file" , new Date (), 0 ), sync . lutime ( "link" , 0 , 0 ), 274sync . link ( "a" , "b" ), sync . truncate ( "file" ), 275]; 276const syncPaths :string [] = [ sync . readLink ( "link" ), sync . realPath ( "." )];