import { SymbolFor } from "ext:core/primordials"; import * as subprocess from "ext:process/main.js"; import * as net from "ext:net/main.js"; import * as file from "ext:file/main.js"; import * as environment from "./env.js"; import * as consoleInspection from "ext:console/inspect.js"; import * as authority from "./pledge.js"; import * as host from "./host.js"; import * as http from "ext:http/main.js"; import * as httpFile from "ext:http/file.js"; import * as stdio from "ext:stdio/main.js"; // Non-constructible class values still expose ordinary function reflection. type ClassStatics = Omit; export const avril: unique symbol = SymbolFor("runtime:avril") as typeof avril; export interface GrantRestriction { readonly allow?: readonly T[]; readonly deny?: readonly T[]; } export interface Pledge { readonly net?: false | GrantRestriction; readonly env?: false | GrantRestriction; readonly sys?: false | GrantRestriction; readonly read?: false | GrantRestriction; readonly write?: false | GrantRestriction; readonly jsExec?: false | GrantRestriction; readonly run?: false | GrantRestriction; readonly ffi?: false | GrantRestriction; } /** Irreversibly narrows authority; omitted categories are unchanged. */ export const pledge: (restrictions: Pledge) => void = authority.pledge; /** Runtime-local environment; named operations require authority for that variable. */ export const env: Readonly<{ get(name: string): string | undefined; has(name: string): boolean; set(name: string, value: string): void; delete(name: string): void; /** Returns an independent, null-prototype snapshot of permitted variables only. */ toObject(): Record; }> = environment.env; /** This process. `pid` and `runtime` need no authority; `cwd()` requires read authority for the working directory, and the other methods each require sys authority for their own name ("hostname", "osRelease", "uptime", "loadavg"). Denied calls throw PermissionDenied. */ export const process: Readonly<{ pid: number; /** Frozen script arguments, excluding runtime options and entry modules; needs no authority. REPL and test sessions accept arguments after `--`. Workers inherit them. */ args: readonly string[]; cwd(): string; hostname(): string; osRelease(): string; /** Seconds since the system booted. */ uptime(): number; /** 1, 5, and 15 minute load averages; [0, 0, 0] on Windows. */ loadavg(): [number, number, number]; }> = host.process; /** The avril build running this process; needs no authority. */ export const runtime: Readonly<{ os: "linux" | "macos" | "windows"; arch: "x86_64" | "aarch64"; /** avril version. */ version: string; /** V8 version. */ v8: string; }> = host.runtime; export interface InspectOptions { /** Nesting levels to expand; `null` or `Infinity` expand as far as avril allows. Defaults to 3. */ depth?: number | null; /** Wrap values in ANSI color escapes. */ colors?: boolean; } /** * Formats a value the way `console.log` and the REPL show it, without invoking getters. * * An object customizes its format with a method keyed by * `Symbol.for("runtime.avril.inspect")`, called as `method(inspect, options)`. * A string result is used verbatim; any other result is inspected in its place. * Inspecting the object again from inside its own method uses the default format. */ export const inspect: (value: unknown, options?: InspectOptions) => string = consoleInspection.inspect; export interface TcpAddr { readonly transport: "tcp"; readonly hostname: string; readonly port: number; } export interface UnixAddr { readonly transport: "unix"; readonly path: string | null; } export interface UdpAddr { readonly transport: "udp"; readonly hostname: string; readonly port: number; } export type StreamAddr = TcpAddr | UnixAddr; export type Addr = StreamAddr | UdpAddr; export interface ServeInfo { readonly remoteAddr: A; } export type ServeHandler = ( request: Request, info: ServeInfo, ) => Response | Promise; export interface ServeOptions { path?: never; /** Defaults to 127.0.0.1. */ hostname?: string; /** Defaults to 8000; 0 selects an available port. */ port?: number; /** Aborting stops the listener and closes active connections immediately. */ signal?: AbortSignal; onListen?: (addr: TcpAddr) => void; /** Handler errors default to a 500 response. */ onError?: (error: unknown) => Response | Promise; } export interface UnixServeOptions extends Omit { path: string; hostname?: never; port?: never; onListen?: (addr: UnixAddr) => void; } export interface ServeModule { fetch: ServeHandler; } export interface HttpServer { readonly addr: A; readonly finished: Promise; /** Stops accepting connections and waits for active responses. */ shutdown(): Promise; } /** Serves HTTP/1.1; TCP requires net authority, Unix sockets require write authority. */ export const serve: { (handler: ServeHandler): HttpServer; (options: ServeOptions, handler: ServeHandler): HttpServer; (options: UnixServeOptions, handler: ServeHandler): HttpServer; } = http.serve; export interface ServeFileOptions { /** Additional headers; Content-Type and ETag override the defaults. */ headers?: HeadersInit; /** Transfers ownership of a supplied FileHandle. Defaults to true; ignored for paths. */ consume?: boolean; } /** Streams a regular file with conditional and single-range support; requires read authority. */ export const serveFile: ( request: Request, source: string | URL | FileHandle, options?: ServeFileOptions, ) => Promise = httpFile.serveFile; export const upgradeWebSocket: ( request: Request, options?: { protocol?: string }, ) => { socket: WebSocket; response: Response } = http.upgradeWebSocket; export interface TcpListenOptions { transport?: "tcp"; hostname?: string; port: number; } export interface TcpConnectOptions extends TcpListenOptions { signal?: AbortSignal; } export interface UnixListenOptions { transport: "unix"; path: string; hostname?: never; port?: never; } export interface UnixConnectOptions extends UnixListenOptions { signal?: AbortSignal; } export interface Conn { readonly localAddr: A; readonly remoteAddr: A; read(buffer: Uint8Array): Promise; write(buffer: Uint8Array): Promise; /** Writes every view in order before resolving; unlike write, success means the whole vector was written. */ writev(buffers: readonly Uint8Array[]): Promise; closeWrite(): Promise; close(): void; [Symbol.dispose](): void; readonly readable: ReadableStream; readonly writable: WritableStream; } export const Conn: ClassStatics & { readonly prototype: Conn; [Symbol.hasInstance](value: unknown): value is Conn; } = net.Conn; export interface Listener extends AsyncIterable> { readonly addr: A; accept(): Promise>; close(): void; [Symbol.dispose](): void; [Symbol.asyncIterator](): AsyncIterator>; } export const Listener: ClassStatics & { readonly prototype: Listener; [Symbol.hasInstance](value: unknown): value is Listener; } = net.Listener; export const connect: { (options: TcpConnectOptions): Promise>; (options: UnixConnectOptions): Promise>; } = net.connect; export const listen: { (options: TcpListenOptions): Listener; (options: UnixListenOptions): Listener; } = net.listen; export interface UdpListenOptions { transport?: "udp"; hostname?: string; port: number; } export interface UdpSendOptions { transport?: "udp"; hostname?: string; port: number; } export interface DatagramConn extends AsyncIterable<[Uint8Array, UdpAddr]> { readonly addr: UdpAddr; receive(buffer?: Uint8Array): Promise<[Uint8Array, UdpAddr]>; send(buffer: Uint8Array, target: UdpSendOptions): Promise; setBroadcast(enabled?: boolean): void; close(): void; [Symbol.dispose](): void; [Symbol.asyncIterator](): AsyncIterator<[Uint8Array, UdpAddr]>; } export const DatagramConn: ClassStatics & { readonly prototype: DatagramConn; [Symbol.hasInstance](value: unknown): value is DatagramConn; } = net.DatagramConn; export const listenDatagram: (options: UdpListenOptions) => DatagramConn = net.listenDatagram; export interface OpenOptions { read?: boolean; write?: boolean; append?: boolean; truncate?: boolean; create?: boolean; createNew?: boolean; mode?: number; } export interface FileInfo { isFile: boolean; isDirectory: boolean; isSymlink: boolean; size: number; mtime: Date | null; atime: Date | null; birthtime: Date | null; mode: number | null; uid: number | null; gid: number | null; } export const SeekMode: Readonly<{ Start: 0; Current: 1; End: 2 }> = file.SeekMode; export interface FileHandle { read(buffer: Uint8Array): Promise; write(buffer: Uint8Array): Promise; /** Writes every view in order before resolving; unlike write, success means the whole vector was written. */ writev(buffers: readonly Uint8Array[]): Promise; seek(offset: number, whence: 0 | 1 | 2): Promise; stat(): Promise; truncate(length?: number): Promise; sync(): Promise; syncData(): Promise; fchmod(mode: number): Promise; fchown(uid: number | null, gid: number | null): Promise; /** Times are seconds since the Unix epoch, like Deno and Node; Dates are accepted. */ futime(atime: number | Date, mtime: number | Date): Promise; /** Acquires an advisory lock, exclusive by default (unlike Deno's shared default). Waiting releases the handle queue, so a lock does not serialize with reads and writes. */ flock(exclusive?: boolean): Promise; funlock(): Promise; /** Synchronous operations share the native queue with async calls; they do not run JS callbacks while waiting. */ readSync(buffer: Uint8Array): number | null; writeSync(buffer: Uint8Array): number; /** Writes every view in order before returning; unlike writeSync, a normal return means the whole vector was written. */ writevSync(buffers: readonly Uint8Array[]): number; seekSync(offset: number, whence: 0 | 1 | 2): number; statSync(): FileInfo; truncateSync(length?: number): void; syncSync(): void; syncDataSync(): void; fchmodSync(mode: number): void; fchownSync(uid: number | null, gid: number | null): void; futimeSync(atime: number | Date, mtime: number | Date): void; /** Like flock(), waiting releases the handle queue, but blocks this isolate. */ flockSync(exclusive?: boolean): void; funlockSync(): void; close(): void; [Symbol.dispose](): void; readonly readable: ReadableStream; readonly writable: WritableStream; } export const FileHandle: ClassStatics & { readonly prototype: FileHandle; [Symbol.hasInstance](value: unknown): value is FileHandle; } = file.FileHandle; export const open: ( path: string | URL, options?: OpenOptions, ) => Promise = file.open; export const create: (path: string | URL) => Promise = file.create; export const stat: (path: string | URL) => Promise = file.stat; export const lstat: (path: string | URL) => Promise = file.lstat; export interface DirEntry { name: string; isFile: boolean; isDirectory: boolean; isSymlink: boolean; } export const readDir: (path: string | URL) => AsyncIterable = file.readDir; export type FsEventKind = | "create" | "modify" | "remove" | "rename" | "rescan"; export interface FsEvent { readonly kind: FsEventKind; /** Absolute paths under the canonicalized watched root. A rename has the source first and destination second; a rescan names each root whose contents should be read again. Events may be coalesced, and access events are not reported. */ readonly paths: readonly string[]; } export interface WatchOptions { /** Watch subdirectories, including ones created later; defaults to true. Ignored for file roots. */ recursive?: boolean; /** Aborting closes the watcher and ends iteration without throwing the abort reason. */ signal?: AbortSignal; } export interface FsWatcher extends AsyncIterable { close(): void; /** Keeps the process alive while the watcher is open, including between iterations; this is the default. */ ref(): void; /** Allows the process to exit even while the watcher is open. */ unref(): void; [Symbol.dispose](): void; [Symbol.asyncIterator](): AsyncIterator; } export const FsWatcher: ClassStatics & { readonly prototype: FsWatcher; [Symbol.hasInstance](value: unknown): value is FsWatcher; } = file.FsWatcher; /** Watches existing files or directories; requires read authority for each root and, for recursive directories, its whole tree. Events are rechecked against live read authority when delivered. A file root stays attached to its path across replacement and recreation; removing or renaming away a directory root ends its watch once every root is gone. */ export const watchFs: ( paths: string | URL | Iterable, options?: WatchOptions, ) => FsWatcher = file.watchFs; export const mkdir: ( path: string | URL, options?: { mode?: number }, ) => Promise = file.mkdir; export const remove: ( path: string | URL, options?: { recursive?: boolean }, ) => Promise = file.remove; /** Requires directory-shaped authority over the source (read/write) and destination (write). Scoped directory renames containing symlinks are not yet implemented. */ export const rename: ( source: string | URL, destination: string | URL, ) => Promise = file.rename; export const copyFile: ( source: string | URL, destination: string | URL, ) => Promise = file.copyFile; /** Stores target text unchanged; requires write at the link and read/write tree authority over its target. Creation on Windows is not yet implemented. */ export const symlink: ( target: string | URL, linkPath: string | URL, options?: { type?: "file" | "dir" }, ) => Promise = file.symlink; /** Reads the link's stored text, not its referent. */ export const readLink: (path: string | URL) => Promise = file.readLink; export const realPath: (path: string | URL) => Promise = file.realPath; /** Changes the file mode bits. Follows links. */ export const chmod: (path: string | URL, mode: number) => Promise = file.chmod; /** Null keeps that id; 4294967295 is rejected. Follows links. */ export const chown: ( path: string | URL, uid: number | null, gid: number | null, ) => Promise = file.chown; /** Like chown but acts on the link itself. */ export const lchown: ( path: string | URL, uid: number | null, gid: number | null, ) => Promise = file.lchown; /** Times are seconds since the Unix epoch, like Deno and Node; Dates are accepted. Follows links. */ export const utime: ( path: string | URL, atime: number | Date, mtime: number | Date, ) => Promise = file.utime; /** Like utime but acts on the link itself; a trailing slash follows instead. */ export const lutime: ( path: string | URL, atime: number | Date, mtime: number | Date, ) => Promise = file.lutime; /** Requires read and write authority over the source and write over the destination; symlink sources are refused. */ export const link: ( oldpath: string | URL, newpath: string | URL, ) => Promise = file.link; /** Sets the file size, zero-filling growth. Follows links. */ export const truncate: (path: string | URL, length?: number) => Promise = file.truncate; export const errors: Readonly< Record< | "NotFound" | "PermissionDenied" | "AlreadyExists" | "NotEmpty" | "CrossDevice" | "NotADirectory" | "IsADirectory" | "InvalidInput" | "BadResource" | "Busy" | "BrokenPipe" | "NotSupported" | "IoError", new (message?: string) => Error > > = file.errors; export type ProcessReason = | "abort" | "timeout" | "output-limit" | "terminated" | "dispose" | "owner" | "io-error" | "drain-timeout"; export interface ProcessStatus { readonly code: number | null; readonly signal: string | null; readonly success: boolean; readonly reason: ProcessReason | null; readonly error: string | null; } export interface ProcessOutput extends ProcessStatus { readonly stdout: T | null; readonly stderr: T | null; } /** File redirections resolve against the child working directory. The file mode is output-only; stdin files accept no mode. */ export type ProcessStdio = "piped" | "inherit" | "null" | { file: string | URL; mode?: "truncate" | "append" | "create-new"; } | { handle: FileHandle }; export type ExtraProcessStdio = { pipe: "parent-to-child" | "child-to-parent"; } | { file: string | URL; mode?: "truncate" | "append" | "create-new"; direction: "parent-to-child" | "child-to-parent"; } | { handle: FileHandle; direction: "parent-to-child" | "child-to-parent" }; export interface ProcessOptions { args?: Iterable; cwd?: string | URL; /** Bare names search only the effective child environment's PATH. False uses a pathname relative to child cwd. */ searchPath?: boolean; clearEnv?: boolean; env?: Record; stdin?: ProcessStdio; stdout?: ProcessStdio; stderr?: ProcessStdio | "stdout"; input?: string | Uint8Array | ReadableStream; encoding?: string; /** False returns nonzero exits instead of throwing; timeout, abort, output-limit, and IO failures still throw. */ check?: boolean; signal?: AbortSignal; timeoutMs?: number; /** Combined limit on output kept in memory for a result: stdout and stderr when collected, and stderr alone while lines() streams stdout. Defaults to 128 MiB; exceeding it terminates the child. */ maxCollectedBytes?: number; /** Maximum unread native queue per stream; defaults to 1 MiB. For spawn's streams, overflow terminates the child; there is no backpressure. run() and runSync() always drain their output, so they block the child instead. */ maxBufferedBytes?: number; termination?: { /** Group termination covers the child's process group. With a terminal attached, children that inherit standard streams instead join the parent's group so terminal IO (including /dev/tty) keeps working; kill() then targets only the child. Unowned SIGINT/SIGTERM stop children gracefully before exiting; a repeat signal shortens that wait. Serve owns its shutdown signals and drains in-flight subprocess work. REPL interruption stops only the session's blocking runSync. On Windows the group is the child's job object, and SIGTERM, SIGINT, SIGQUIT, and SIGKILL all terminate at once with exit code 1 and no signal, so graceMs has no effect; other signals reject with NotSupported. Console Ctrl-C also reaches Windows children directly, as a shared terminal's interrupt does on Unix. */ scope?: "group" | "child"; signal?: string; graceMs?: number; }; drainTimeoutMs?: number; } export interface SpawnOptions extends ProcessOptions { /** On Windows, descriptors up to 254 reach children through the C runtime's inherited descriptor table, so only CRT-based programs see them. */ extraStdio?: Record; /** Not yet supported on Windows. */ terminal?: { columns?: number; rows?: number }; } export interface ProcessTerminal { readonly readable: ReadableStream; readonly writable: WritableStream; resize(columns: number, rows: number): void; } /** One native invocation, owned by this isolate. Native execution is not sandboxed. On Linux, shebang scripts observe /dev/fd/N as their path while the executable stays pinned. */ export interface ChildProcess extends AsyncDisposable { readonly pid: number; /** Null when automatic input owns stdin. */ readonly stdin: WritableStream | null; readonly stdout: ReadableStream | null; readonly stderr: ReadableStream | null; readonly stdio: Readonly< Record | WritableStream> >; readonly terminal: ProcessTerminal | null; readonly status: Promise; readonly completed: Promise; /** Collects once, never respawns; rejects previously read or locked output. */ output(): Promise>; /** Sends one signal to the termination scope (the whole process group by default, or just the child when it shares the parent's group). */ kill(signal?: string): boolean; terminate(): Promise; ref(): void; unref(): void; [Symbol.asyncDispose](): Promise; } export const ChildProcess: ClassStatics & { readonly prototype: ChildProcess; [Symbol.hasInstance](value: unknown): value is ChildProcess; } = subprocess.ChildProcess; export interface ProcessError extends Error { readonly result: ProcessOutput | null; } export const ProcessError: Omit & { readonly prototype: ProcessError; new ( message: string, result?: ProcessOutput | null, cause?: unknown, ): ProcessError; [Symbol.hasInstance](value: unknown): value is ProcessError; } = subprocess.ProcessError; export interface OutputLimitError extends ProcessError { } export const OutputLimitError: Omit & { readonly prototype: OutputLimitError; new (result: ProcessOutput): OutputLimitError; [Symbol.hasInstance](value: unknown): value is OutputLimitError; } = subprocess.OutputLimitError; export interface ProcessTimeoutError extends ProcessError { } export const ProcessTimeoutError: Omit & { readonly prototype: ProcessTimeoutError; new (result: ProcessOutput): ProcessTimeoutError; [Symbol.hasInstance](value: unknown): value is ProcessTimeoutError; } = subprocess.ProcessTimeoutError; export interface ProcessAbortError extends ProcessError { } export const ProcessAbortError: Omit & { readonly prototype: ProcessAbortError; new ( result: ProcessOutput | null, cause?: unknown, ): ProcessAbortError; [Symbol.hasInstance](value: unknown): value is ProcessAbortError; } = subprocess.ProcessAbortError; /** Authorizes native execution through run capabilities, independently of read and jsExec; the child is not sandboxed by avril's capabilities. Supported on Linux, macOS, and Windows 10 1607 or later; other platforms reject launch with NotSupported. On Linux, when close_range is unavailable, descriptor isolation requires /proc/self/fd. Linux pins the executable through exec; macOS launches the authorized path, and Windows holds it open against replacement until launch. Abruptly killed owners take their direct children with them on Linux and the child's job on Windows; on macOS only orderly exits clean up. Windows rejects batch files (run cmd.exe explicitly), and names without a .com or .exe extension also try both. Grants and denials name executable entries, not programs: aliases and hard links to the same binary need their own entries. On case-insensitive filesystems they match an entry by its stored spelling. Relative grants resolve against the grant-time working directory, not PATH. Unref'd children are terminated when their owner exits; only direct children are pinned to the owner's lifetime, so group grandchildren rely on group termination. Unix supervision preserves inherited ignored signal dispositions. No implicit shell. */ export const run: { ( command: string | URL, options?: ProcessOptions & { encoding?: undefined }, ): ProcessPromise; ( command: string | URL, options: ProcessOptions & { encoding: string }, ): ProcessPromise; ( command: string | URL, args: Iterable | undefined, options?: ProcessOptions & { args?: undefined; encoding?: undefined }, ): ProcessPromise; ( command: string | URL, args: Iterable | undefined, options: ProcessOptions & { args?: undefined; encoding: string }, ): ProcessPromise; } = subprocess.run; /** The run() result, with shortcuts to its stdout. Each shortcut fails like the run itself; stdout decodes with the encoding option, or UTF-8. */ export interface ProcessPromise extends Promise> { bytes(): Promise; text(): Promise; json(): Promise; /** Stdout lines without their \n or \r\n. Called in the same turn as run(), it streams as the child writes, and the run's failures surface after the last line; a stopped run (timeout, abort, termination) ends iteration without the lines still queued, and ending iteration early terminates the child, so the run rejects as terminated. Past maxBufferedBytes unread, the child blocks until the loop catches up, so iterate rather than only awaiting the run. Streamed stdout is not collected and does not count toward maxCollectedBytes. Called later, it splits the collected output. */ lines(): AsyncIterableIterator; } export type RunSyncOptions = Omit & { input?: string | Uint8Array; }; export const runSync: { ( command: string | URL, options?: RunSyncOptions & { encoding?: undefined }, ): ProcessOutput; ( command: string | URL, options: RunSyncOptions & { encoding: string }, ): ProcessOutput; ( command: string | URL, args: Iterable | undefined, options?: RunSyncOptions & { args?: undefined; encoding?: undefined }, ): ProcessOutput; ( command: string | URL, args: Iterable | undefined, options: RunSyncOptions & { args?: undefined; encoding: string }, ): ProcessOutput; } = subprocess.runSync; export const spawn: { ( command: string | URL, options?: SpawnOptions & { encoding?: undefined }, ): ChildProcess; ( command: string | URL, options: SpawnOptions & { encoding: string }, ): ChildProcess; ( command: string | URL, args: Iterable | undefined, options?: SpawnOptions & { args?: undefined; encoding?: undefined }, ): ChildProcess; ( command: string | URL, args: Iterable | undefined, options: SpawnOptions & { args?: undefined; encoding: string }, ): ChildProcess; } = subprocess.spawn; /** * Listens for a process signal, Deno-style. Supported on Linux and * macOS for SIGINT, SIGTERM, SIGHUP, SIGUSR1, SIGUSR2, and SIGWINCH; * other platforms and other signals throw NotSupported, while * unknown names and the uncatchable SIGKILL/SIGSTOP throw * InvalidInput. Listening needs no pledge. * * Signals are process-wide: each delivery reaches every live isolate * that binds it (entry, REPL, workers), with one isolate's listeners * firing in binding order. But only the entry isolate (and the REPL's * main isolate) suppress the default death: worker listeners observe * every delivery but never count, so with no entry listener a lethal * delivery still kills the process and worker delivery is best-effort. * A worker that must finish cleanup cannot rely on its own listener; * the entry module must listen instead and coordinate, the same shape * as serve's shutdown: entry listens, notifies workers, waits for * their acks, then exits. Once the last entry listener goes away the * prior fate returns. An active listener keeps the event loop alive, * like a referenced handle. * * Reserved owners still run alongside listeners: serve owns SIGINT and * SIGTERM for shutdown, and the REPL owns SIGINT for eval interrupt, so * listening to those observes the shutdown or interrupt rather than * replacing it. */ export const addSignalListener: ( signal: string, listener: () => void, ) => void = subprocess.addSignalListener; /** Stops a listener added with addSignalListener; unknown pairs are a no-op. */ export const removeSignalListener: ( signal: string, listener: () => void, ) => void = subprocess.removeSignalListener; export const stdin: ReadableStream = stdio.stdin; export const stdout: WritableStream = stdio.stdout; export const stderr: WritableStream = stdio.stderr;