diff --git a/cli/ops/mod.rs b/cli/ops/mod.rs index df33313534..c02b951b20 100644 --- a/cli/ops/mod.rs +++ b/cli/ops/mod.rs @@ -1,7 +1,6 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. use crate::proc_state::ProcState; -use deno_core::anyhow::bail; use deno_core::error::AnyError; use deno_core::op; use deno_core::Extension; @@ -27,10 +26,5 @@ fn init_proc_state(ps: ProcState) -> Extension { #[op] fn op_npm_process_state(state: &mut OpState) -> Result { let proc_state = state.borrow_mut::(); - if !proc_state.options.unstable() { - bail!( - "Unstable use of npm process state. The --unstable flag must be provided." - ) - } Ok(proc_state.npm_resolver.get_npm_process_state()) } diff --git a/cli/proc_state.rs b/cli/proc_state.rs index 117451c568..29f4ce3bd9 100644 --- a/cli/proc_state.rs +++ b/cli/proc_state.rs @@ -274,6 +274,7 @@ impl ProcState { /// module before attempting to `load()` it from a `JsRuntime`. It will /// populate `self.graph_data` in memory with the necessary source code, write /// emits where necessary or report any module graph / type checking errors. + #[allow(clippy::too_many_arguments)] pub async fn prepare_module_load( &self, roots: Vec, diff --git a/cli/tests/integration/npm_tests.rs b/cli/tests/integration/npm_tests.rs index 1eb476287d..ced9ad8a3e 100644 --- a/cli/tests/integration/npm_tests.rs +++ b/cli/tests/integration/npm_tests.rs @@ -103,7 +103,7 @@ itest!(dual_cjs_esm { }); itest!(child_process_fork_test { - args: "run --unstable -A --quiet npm/child_process_fork_test/main.ts", + args: "run -A --quiet npm/child_process_fork_test/main.ts", output: "npm/child_process_fork_test/main.out", envs: env_vars(), http_server: true, @@ -621,7 +621,7 @@ itest!(node_modules_dir_with_deps { }); itest!(node_modules_dir_yargs { - args: "run --allow-read --allow-env --unstable --node-modules-dir $TESTDATA/npm/cjs_yargs/main.js", + args: "run --allow-read --allow-env --node-modules-dir $TESTDATA/npm/cjs_yargs/main.js", output: "npm/cjs_yargs/main.out", envs: env_vars(), http_server: true, @@ -1050,7 +1050,6 @@ fn peer_deps_with_copied_folders_and_lockfile() { let deno = util::deno_cmd_with_deno_dir(&deno_dir) .current_dir(temp_dir.path()) .arg("run") - .arg("--unstable") .arg("-A") .arg("main.ts") .envs(env_vars()) @@ -1082,7 +1081,6 @@ fn peer_deps_with_copied_folders_and_lockfile() { let deno = util::deno_cmd_with_deno_dir(&deno_dir) .current_dir(temp_dir.path()) .arg("run") - .arg("--unstable") .arg("-A") .arg("main.ts") .envs(env_vars()) @@ -1097,7 +1095,6 @@ fn peer_deps_with_copied_folders_and_lockfile() { let deno = util::deno_cmd_with_deno_dir(&deno_dir) .current_dir(temp_dir.path()) .arg("run") - .arg("--unstable") .arg("--reload") .arg("-A") .arg("main.ts") @@ -1114,7 +1111,6 @@ fn peer_deps_with_copied_folders_and_lockfile() { let deno = util::deno_cmd_with_deno_dir(&deno_dir) .current_dir(temp_dir.path()) .arg("run") - .arg("--unstable") .arg("--node-modules-dir") .arg("-A") .arg("main.ts") @@ -1139,7 +1135,6 @@ fn peer_deps_with_copied_folders_and_lockfile() { let deno = util::deno_cmd_with_deno_dir(&deno_dir) .current_dir(temp_dir.path()) .arg("run") - .arg("--unstable") .arg("--node-modules-dir") .arg("-A") .arg("main.ts") @@ -1156,7 +1151,6 @@ fn peer_deps_with_copied_folders_and_lockfile() { let deno = util::deno_cmd_with_deno_dir(&deno_dir) .current_dir(temp_dir.path()) .arg("run") - .arg("--unstable") .arg("--node-modules-dir") .arg("--reload") .arg("-A") @@ -1174,7 +1168,6 @@ fn peer_deps_with_copied_folders_and_lockfile() { let deno = util::deno_cmd_with_deno_dir(&deno_dir) .current_dir(temp_dir.path()) .arg("run") - .arg("--unstable") .arg("--node-modules-dir") .arg("--no-lock") .arg("--reload") diff --git a/ext/flash/01_http.js b/ext/flash/01_http.js index 4435860ff9..7a6b9bc47e 100644 --- a/ext/flash/01_http.js +++ b/ext/flash/01_http.js @@ -422,239 +422,241 @@ })(); } - async function serve(arg1, arg2) { - let options = undefined; - let handler = undefined; - if (arg1 instanceof Function) { - handler = arg1; - options = arg2; - } else if (arg2 instanceof Function) { - handler = arg2; - options = arg1; - } else { - options = arg1; - } - if (handler === undefined) { - if (options === undefined) { - throw new TypeError( - "No handler was provided, so an options bag is mandatory.", - ); + function createServe(opFn) { + return async function serve(arg1, arg2) { + let options = undefined; + let handler = undefined; + if (arg1 instanceof Function) { + handler = arg1; + options = arg2; + } else if (arg2 instanceof Function) { + handler = arg2; + options = arg1; + } else { + options = arg1; } - handler = options.handler; - } - if (!(handler instanceof Function)) { - throw new TypeError("A handler function must be provided."); - } - if (options === undefined) { - options = {}; - } - - const signal = options.signal; - - const onError = options.onError ?? function (error) { - console.error(error); - return new Response("Internal Server Error", { status: 500 }); - }; - - const onListen = options.onListen ?? function ({ port }) { - console.log( - `Listening on http://${ - hostnameForDisplay(listenOpts.hostname) - }:${port}/`, - ); - }; - - const listenOpts = { - hostname: options.hostname ?? "127.0.0.1", - port: options.port ?? 9000, - reuseport: options.reusePort ?? false, - }; - if (options.cert || options.key) { - if (!options.cert || !options.key) { - throw new TypeError( - "Both cert and key must be provided to enable HTTPS.", - ); - } - listenOpts.cert = options.cert; - listenOpts.key = options.key; - } - - const serverId = core.ops.op_flash_serve(listenOpts); - const serverPromise = core.opAsync("op_flash_drive_server", serverId); - - PromisePrototypeCatch( - PromisePrototypeThen( - core.opAsync("op_flash_wait_for_listening", serverId), - (port) => { - onListen({ hostname: listenOpts.hostname, port }); - }, - ), - () => {}, - ); - const finishedPromise = PromisePrototypeCatch(serverPromise, () => {}); - - const server = { - id: serverId, - transport: listenOpts.cert && listenOpts.key ? "https" : "http", - hostname: listenOpts.hostname, - port: listenOpts.port, - closed: false, - finished: finishedPromise, - async close() { - if (server.closed) { - return; + if (handler === undefined) { + if (options === undefined) { + throw new TypeError( + "No handler was provided, so an options bag is mandatory.", + ); } - server.closed = true; - await core.opAsync("op_flash_close_server", serverId); - await server.finished; - }, - async serve() { - let offset = 0; - while (true) { - if (server.closed) { - break; - } + handler = options.handler; + } + if (!(handler instanceof Function)) { + throw new TypeError("A handler function must be provided."); + } + if (options === undefined) { + options = {}; + } - let tokens = nextRequestSync(); - if (tokens === 0) { - tokens = await core.opAsync("op_flash_next_async", serverId); + const signal = options.signal; + + const onError = options.onError ?? function (error) { + console.error(error); + return new Response("Internal Server Error", { status: 500 }); + }; + + const onListen = options.onListen ?? function ({ port }) { + console.log( + `Listening on http://${ + hostnameForDisplay(listenOpts.hostname) + }:${port}/`, + ); + }; + + const listenOpts = { + hostname: options.hostname ?? "127.0.0.1", + port: options.port ?? 9000, + reuseport: options.reusePort ?? false, + }; + if (options.cert || options.key) { + if (!options.cert || !options.key) { + throw new TypeError( + "Both cert and key must be provided to enable HTTPS.", + ); + } + listenOpts.cert = options.cert; + listenOpts.key = options.key; + } + + const serverId = opFn(listenOpts); + const serverPromise = core.opAsync("op_flash_drive_server", serverId); + + PromisePrototypeCatch( + PromisePrototypeThen( + core.opAsync("op_flash_wait_for_listening", serverId), + (port) => { + onListen({ hostname: listenOpts.hostname, port }); + }, + ), + () => {}, + ); + const finishedPromise = PromisePrototypeCatch(serverPromise, () => {}); + + const server = { + id: serverId, + transport: listenOpts.cert && listenOpts.key ? "https" : "http", + hostname: listenOpts.hostname, + port: listenOpts.port, + closed: false, + finished: finishedPromise, + async close() { + if (server.closed) { + return; + } + server.closed = true; + await core.opAsync("op_flash_close_server", serverId); + await server.finished; + }, + async serve() { + let offset = 0; + while (true) { if (server.closed) { break; } - } - for (let i = offset; i < offset + tokens; i++) { - let body = null; - // There might be a body, but we don't expose it for GET/HEAD requests. - // It will be closed automatically once the request has been handled and - // the response has been sent. - const method = getMethodSync(i); - let hasBody = method > 2; // Not GET/HEAD/CONNECT - if (hasBody) { - body = createRequestBodyStream(serverId, i); - if (body === null) { - hasBody = false; + let tokens = nextRequestSync(); + if (tokens === 0) { + tokens = await core.opAsync("op_flash_next_async", serverId); + if (server.closed) { + break; } } - const req = fromFlashRequest( - serverId, - /* streamRid */ - i, - body, - /* methodCb */ - () => methods[method], - /* urlCb */ - () => { - const path = core.ops.op_flash_path(serverId, i); - return `${server.transport}://${server.hostname}:${server.port}${path}`; - }, - /* headersCb */ - () => core.ops.op_flash_headers(serverId, i), - ); - - let resp; - try { - resp = handler(req); - if (resp instanceof Promise) { - PromisePrototypeCatch( - PromisePrototypeThen( - resp, - (resp) => - handleResponse( - req, - resp, - body, - hasBody, - method, - serverId, - i, - respondFast, - respondChunked, - ), - ), - onError, - ); - continue; - } else if (typeof resp?.then === "function") { - resp.then((resp) => - handleResponse( - req, - resp, - body, - hasBody, - method, - serverId, - i, - respondFast, - respondChunked, - ) - ).catch(onError); - continue; + for (let i = offset; i < offset + tokens; i++) { + let body = null; + // There might be a body, but we don't expose it for GET/HEAD requests. + // It will be closed automatically once the request has been handled and + // the response has been sent. + const method = getMethodSync(i); + let hasBody = method > 2; // Not GET/HEAD/CONNECT + if (hasBody) { + body = createRequestBodyStream(serverId, i); + if (body === null) { + hasBody = false; + } } - } catch (e) { - resp = await onError(e); + + const req = fromFlashRequest( + serverId, + /* streamRid */ + i, + body, + /* methodCb */ + () => methods[method], + /* urlCb */ + () => { + const path = core.ops.op_flash_path(serverId, i); + return `${server.transport}://${server.hostname}:${server.port}${path}`; + }, + /* headersCb */ + () => core.ops.op_flash_headers(serverId, i), + ); + + let resp; + try { + resp = handler(req); + if (resp instanceof Promise) { + PromisePrototypeCatch( + PromisePrototypeThen( + resp, + (resp) => + handleResponse( + req, + resp, + body, + hasBody, + method, + serverId, + i, + respondFast, + respondChunked, + ), + ), + onError, + ); + continue; + } else if (typeof resp?.then === "function") { + resp.then((resp) => + handleResponse( + req, + resp, + body, + hasBody, + method, + serverId, + i, + respondFast, + respondChunked, + ) + ).catch(onError); + continue; + } + } catch (e) { + resp = await onError(e); + } + + handleResponse( + req, + resp, + body, + hasBody, + method, + serverId, + i, + respondFast, + respondChunked, + ); } - handleResponse( - req, - resp, - body, - hasBody, - method, - serverId, - i, - respondFast, - respondChunked, - ); + offset += tokens; } + await server.finished; + }, + }; - offset += tokens; - } - await server.finished; - }, - }; + signal?.addEventListener("abort", () => { + clearInterval(dateInterval); + PromisePrototypeThen(server.close(), () => {}, () => {}); + }, { + once: true, + }); - signal?.addEventListener("abort", () => { - clearInterval(dateInterval); - PromisePrototypeThen(server.close(), () => {}, () => {}); - }, { - once: true, - }); + function respondChunked(token, chunk, shutdown) { + return core.opAsync( + "op_flash_respond_chuncked", + serverId, + token, + chunk, + shutdown, + ); + } - function respondChunked(token, chunk, shutdown) { - return core.opAsync( - "op_flash_respond_chuncked", - serverId, - token, - chunk, - shutdown, - ); - } + const fastOp = prepareFastCalls(); + let nextRequestSync = () => fastOp.nextRequest(); + let getMethodSync = (token) => fastOp.getMethod(token); + let respondFast = (token, response, shutdown) => + fastOp.respond(token, response, shutdown); + if (serverId > 0) { + nextRequestSync = () => core.ops.op_flash_next_server(serverId); + getMethodSync = (token) => core.ops.op_flash_method(serverId, token); + respondFast = (token, response, shutdown) => + core.ops.op_flash_respond(serverId, token, response, null, shutdown); + } - const fastOp = prepareFastCalls(); - let nextRequestSync = () => fastOp.nextRequest(); - let getMethodSync = (token) => fastOp.getMethod(token); - let respondFast = (token, response, shutdown) => - fastOp.respond(token, response, shutdown); - if (serverId > 0) { - nextRequestSync = () => core.ops.op_flash_next_server(serverId); - getMethodSync = (token) => core.ops.op_flash_method(serverId, token); - respondFast = (token, response, shutdown) => - core.ops.op_flash_respond(serverId, token, response, null, shutdown); - } - - if (!dateInterval) { - date = new Date().toUTCString(); - dateInterval = setInterval(() => { + if (!dateInterval) { date = new Date().toUTCString(); - }, 1000); - } + dateInterval = setInterval(() => { + date = new Date().toUTCString(); + }, 1000); + } - await SafePromiseAll([ - PromisePrototypeCatch(server.serve(), console.error), - serverPromise, - ]); + await SafePromiseAll([ + PromisePrototypeCatch(server.serve(), console.error), + serverPromise, + ]); + }; } function createRequestBodyStream(serverId, token) { @@ -722,7 +724,7 @@ } window.__bootstrap.flash = { - serve, + createServe, upgradeHttpRaw, }; })(this); diff --git a/ext/flash/lib.rs b/ext/flash/lib.rs index 1f3686760d..b077b8d219 100644 --- a/ext/flash/lib.rs +++ b/ext/flash/lib.rs @@ -1171,15 +1171,13 @@ pub fn resolve_addr_sync( Ok(result) } -#[op] -fn op_flash_serve

( +fn flash_serve

( state: &mut OpState, opts: ListenOpts, ) -> Result where P: FlashPermissions + 'static, { - check_unstable(state, "Deno.serve"); state .borrow_mut::

() .check_net(&(&opts.hostname, Some(opts.port)), "Deno.serve()")?; @@ -1223,6 +1221,29 @@ where Ok(server_id) } +#[op] +fn op_flash_serve

( + state: &mut OpState, + opts: ListenOpts, +) -> Result +where + P: FlashPermissions + 'static, +{ + check_unstable(state, "Deno.serve"); + flash_serve::

(state, opts) +} + +#[op] +fn op_node_unstable_flash_serve

( + state: &mut OpState, + opts: ListenOpts, +) -> Result +where + P: FlashPermissions + 'static, +{ + flash_serve::

(state, opts) +} + #[op] fn op_flash_wait_for_listening( state: &mut OpState, @@ -1445,6 +1466,7 @@ pub fn init(unstable: bool) -> Extension { )) .ops(vec![ op_flash_serve::decl::

(), + op_node_unstable_flash_serve::decl::

(), op_flash_respond::decl(), op_flash_respond_async::decl(), op_flash_respond_chuncked::decl(), diff --git a/ext/net/01_net.js b/ext/net/01_net.js index 765b940351..971ec2e8bf 100644 --- a/ext/net/01_net.js +++ b/ext/net/01_net.js @@ -319,30 +319,32 @@ } } - function listenDatagram(args) { - switch (args.transport) { - case "udp": { - const [rid, addr] = ops.op_net_listen_udp( - { - hostname: args.hostname ?? "127.0.0.1", - port: args.port, - }, - args.reuseAddress ?? false, - ); - addr.transport = "udp"; - return new Datagram(rid, addr); + function createListenDatagram(udpOpFn, unixOpFn) { + return function listenDatagram(args) { + switch (args.transport) { + case "udp": { + const [rid, addr] = udpOpFn( + { + hostname: args.hostname ?? "127.0.0.1", + port: args.port, + }, + args.reuseAddress ?? false, + ); + addr.transport = "udp"; + return new Datagram(rid, addr); + } + case "unixpacket": { + const [rid, path] = unixOpFn(args.path); + const addr = { + transport: "unixpacket", + path, + }; + return new Datagram(rid, addr); + } + default: + throw new TypeError(`Unsupported transport: '${transport}'`); } - case "unixpacket": { - const [rid, path] = ops.op_net_listen_unixpacket(args.path); - const addr = { - transport: "unixpacket", - path, - }; - return new Datagram(rid, addr); - } - default: - throw new TypeError(`Unsupported transport: '${transport}'`); - } + }; } async function connect(args) { @@ -389,7 +391,7 @@ TcpConn, UnixConn, listen, - listenDatagram, + createListenDatagram, Listener, shutdown, Datagram, diff --git a/ext/net/ops.rs b/ext/net/ops.rs index e6420bf9e6..96de8cff13 100644 --- a/ext/net/ops.rs +++ b/ext/net/ops.rs @@ -53,10 +53,13 @@ pub fn init() -> Vec { crate::ops_unix::op_net_connect_unix::decl::

(), op_net_listen_tcp::decl::

(), op_net_listen_udp::decl::

(), + op_node_unstable_net_listen_udp::decl::

(), #[cfg(unix)] crate::ops_unix::op_net_listen_unix::decl::

(), #[cfg(unix)] crate::ops_unix::op_net_listen_unixpacket::decl::

(), + #[cfg(unix)] + crate::ops_unix::op_node_unstable_net_listen_unixpacket::decl::

(), op_net_recv_udp::decl(), #[cfg(unix)] crate::ops_unix::op_net_recv_unixpacket::decl(), @@ -288,8 +291,7 @@ where Ok((rid, IpAddr::from(local_addr))) } -#[op] -fn op_net_listen_udp( +fn net_listen_udp( state: &mut OpState, addr: IpAddr, reuse_address: bool, @@ -297,7 +299,6 @@ fn op_net_listen_udp( where NP: NetPermissions + 'static, { - super::check_unstable(state, "Deno.listenDatagram"); state .borrow_mut::() .check_net(&(&addr.hostname, Some(addr.port)), "Deno.listenDatagram()")?; @@ -343,6 +344,32 @@ where Ok((rid, IpAddr::from(local_addr))) } +#[op] +fn op_net_listen_udp( + state: &mut OpState, + addr: IpAddr, + reuse_address: bool, +) -> Result<(ResourceId, IpAddr), AnyError> +where + NP: NetPermissions + 'static, +{ + super::check_unstable(state, "Deno.listenDatagram"); + net_listen_udp::(state, addr, reuse_address) +} + +#[op] +fn op_node_unstable_net_listen_udp( + state: &mut OpState, + addr: IpAddr, + reuse_address: bool, +) -> Result<(ResourceId, IpAddr), AnyError> +where + NP: NetPermissions + 'static, +{ + super::check_unstable(state, "Deno.listenDatagram"); + net_listen_udp::(state, addr, reuse_address) +} + #[derive(Serialize, Eq, PartialEq, Debug)] #[serde(untagged)] pub enum DnsReturnRecord { diff --git a/ext/net/ops_unix.rs b/ext/net/ops_unix.rs index b45b023432..bf03f40151 100644 --- a/ext/net/ops_unix.rs +++ b/ext/net/ops_unix.rs @@ -209,8 +209,7 @@ where Ok((rid, pathname)) } -#[op] -pub fn op_net_listen_unixpacket( +pub fn net_listen_unixpacket( state: &mut OpState, path: String, ) -> Result<(ResourceId, Option), AnyError> @@ -218,7 +217,6 @@ where NP: NetPermissions + 'static, { let address_path = Path::new(&path); - super::check_unstable(state, "Deno.listenDatagram"); let permissions = state.borrow_mut::(); permissions.check_read(address_path, "Deno.listenDatagram()")?; permissions.check_write(address_path, "Deno.listenDatagram()")?; @@ -233,6 +231,29 @@ where Ok((rid, pathname)) } +#[op] +pub fn op_net_listen_unixpacket( + state: &mut OpState, + path: String, +) -> Result<(ResourceId, Option), AnyError> +where + NP: NetPermissions + 'static, +{ + super::check_unstable(state, "Deno.listenDatagram"); + net_listen_unixpacket::(state, path) +} + +#[op] +pub fn op_node_unstable_net_listen_unixpacket( + state: &mut OpState, + path: String, +) -> Result<(ResourceId, Option), AnyError> +where + NP: NetPermissions + 'static, +{ + net_listen_unixpacket::(state, path) +} + pub fn pathstring(pathname: &Path) -> Result { into_string(pathname.into()) } diff --git a/runtime/js/40_spawn.js b/runtime/js/40_spawn.js index a927d619ed..4d2fb1607e 100644 --- a/runtime/js/40_spawn.js +++ b/runtime/js/40_spawn.js @@ -26,7 +26,7 @@ const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId"); - function spawnChildInner(command, apiName, { + function spawnChildInner(opFn, command, apiName, { args = [], cwd = undefined, clearEnv = false, @@ -39,7 +39,7 @@ signal = undefined, windowsRawArguments = false, } = {}) { - const child = ops.op_spawn_child({ + const child = opFn({ cmd: pathFromURL(command), args: ArrayPrototypeMap(args, String), cwd: pathFromURL(cwd), @@ -58,8 +58,10 @@ }); } - function spawnChild(command, options = {}) { - return spawnChildInner(command, "Deno.spawnChild()", options); + function createSpawnChild(opFn) { + return function spawnChild(command, options = {}) { + return spawnChildInner(opFn, command, "Deno.spawnChild()", options); + }; } async function collectOutput(readableStream) { @@ -227,68 +229,72 @@ } } - function spawn(command, options) { - if (options?.stdin === "piped") { - throw new TypeError( - "Piped stdin is not supported for this function, use 'Deno.spawnChild()' instead", - ); - } - return spawnChildInner(command, "Deno.spawn()", options).output(); + function createSpawn(opFn) { + return function spawn(command, options) { + if (options?.stdin === "piped") { + throw new TypeError( + "Piped stdin is not supported for this function, use 'Deno.spawnChild()' instead", + ); + } + return spawnChildInner(opFn, command, "Deno.spawn()", options).output(); + }; } - function spawnSync(command, { - args = [], - cwd = undefined, - clearEnv = false, - env = {}, - uid = undefined, - gid = undefined, - stdin = "null", - stdout = "piped", - stderr = "piped", - windowsRawArguments = false, - } = {}) { - if (stdin === "piped") { - throw new TypeError( - "Piped stdin is not supported for this function, use 'Deno.spawnChild()' instead", - ); - } - const result = ops.op_spawn_sync({ - cmd: pathFromURL(command), - args: ArrayPrototypeMap(args, String), - cwd: pathFromURL(cwd), - clearEnv, - env: ObjectEntries(env), - uid, - gid, - stdin, - stdout, - stderr, - windowsRawArguments, - }); - return { - success: result.status.success, - code: result.status.code, - signal: result.status.signal, - get stdout() { - if (result.stdout == null) { - throw new TypeError("stdout is not piped"); - } - return result.stdout; - }, - get stderr() { - if (result.stderr == null) { - throw new TypeError("stderr is not piped"); - } - return result.stderr; - }, + function createSpawnSync(opFn) { + return function spawnSync(command, { + args = [], + cwd = undefined, + clearEnv = false, + env = {}, + uid = undefined, + gid = undefined, + stdin = "null", + stdout = "piped", + stderr = "piped", + windowsRawArguments = false, + } = {}) { + if (stdin === "piped") { + throw new TypeError( + "Piped stdin is not supported for this function, use 'Deno.spawnChild()' instead", + ); + } + const result = opFn({ + cmd: pathFromURL(command), + args: ArrayPrototypeMap(args, String), + cwd: pathFromURL(cwd), + clearEnv, + env: ObjectEntries(env), + uid, + gid, + stdin, + stdout, + stderr, + windowsRawArguments, + }); + return { + success: result.status.success, + code: result.status.code, + signal: result.status.signal, + get stdout() { + if (result.stdout == null) { + throw new TypeError("stdout is not piped"); + } + return result.stdout; + }, + get stderr() { + if (result.stderr == null) { + throw new TypeError("stderr is not piped"); + } + return result.stderr; + }, + }; }; } window.__bootstrap.spawn = { Child, - spawnChild, - spawn, - spawnSync, + createSpawn, + createSpawnChild, + createSpawnSync, }; })(this); diff --git a/runtime/js/90_deno_ns.js b/runtime/js/90_deno_ns.js index 64653d4693..033ad421ea 100644 --- a/runtime/js/90_deno_ns.js +++ b/runtime/js/90_deno_ns.js @@ -4,6 +4,7 @@ ((window) => { const core = window.Deno.core; const __bootstrap = window.__bootstrap; + __bootstrap.denoNs = { metrics: core.metrics, test: __bootstrap.testing.test, diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index ef99969b22..9b4a9e8572 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -700,6 +700,7 @@ delete Intl.v8BreakIterator; const wrapConsole = window.__bootstrap.console.wrapConsole; // Remove bootstrapping data from the global scope + const __bootstrap = globalThis.__bootstrap; delete globalThis.__bootstrap; delete globalThis.bootstrap; util.log("bootstrapMainRuntime"); @@ -755,6 +756,27 @@ delete Intl.v8BreakIterator; const internalSymbol = Symbol("Deno.internal"); + // These have to initialized here and not in `90_deno_ns.js` because + // the op function that needs to be passed will be invalidated by creating + // a snapshot + ObjectAssign(internals, { + nodeUnstable: { + spawnChild: __bootstrap.spawn.createSpawnChild( + ops.op_node_unstable_spawn_child, + ), + spawn: __bootstrap.spawn.createSpawn(ops.op_node_unstable_spawn_child), + spawnSync: __bootstrap.spawn.createSpawnSync( + ops.op_node_unstable_spawn_sync, + ), + serve: __bootstrap.flash.createServe(ops.op_node_unstable_flash_serve), + upgradeHttpRaw: __bootstrap.flash.upgradeHttpRaw, + listenDatagram: __bootstrap.net.createListenDatagram( + ops.op_node_unstable_net_listen_udp, + ops.op_node_unstable_net_listen_unixpacket, + ), + }, + }); + const finalDenoNs = { core, internal: internalSymbol, @@ -773,6 +795,19 @@ delete Intl.v8BreakIterator; if (runtimeOptions.unstableFlag) { ObjectAssign(finalDenoNs, denoNsUnstable); + // These have to initialized here and not in `90_deno_ns.js` because + // the op function that needs to be passed will be invalidated by creating + // a snapshot + ObjectAssign(finalDenoNs, { + spawnChild: __bootstrap.spawn.createSpawnChild(ops.op_spawn_child), + spawn: __bootstrap.spawn.createSpawn(ops.op_spawn_child), + spawnSync: __bootstrap.spawn.createSpawnSync(ops.op_spawn_sync), + serve: __bootstrap.flash.createServe(ops.op_flash_serve), + listenDatagram: __bootstrap.net.createListenDatagram( + ops.op_net_listen_udp, + ops.op_net_listen_unixpacket, + ), + }); } // Setup `Deno` global - we're actually overriding already existing global @@ -800,6 +835,7 @@ delete Intl.v8BreakIterator; const wrapConsole = window.__bootstrap.console.wrapConsole; // Remove bootstrapping data from the global scope + const __bootstrap = globalThis.__bootstrap; delete globalThis.__bootstrap; delete globalThis.bootstrap; util.log("bootstrapWorkerRuntime"); @@ -849,6 +885,27 @@ delete Intl.v8BreakIterator; const internalSymbol = Symbol("Deno.internal"); + // These have to initialized here and not in `90_deno_ns.js` because + // the op function that needs to be passed will be invalidated by creating + // a snapshot + ObjectAssign(internals, { + nodeUnstable: { + spawnChild: __bootstrap.spawn.createSpawnChild( + ops.op_node_unstable_spawn_child, + ), + spawn: __bootstrap.spawn.createSpawn(ops.op_node_unstable_spawn_child), + spawnSync: __bootstrap.spawn.createSpawnSync( + ops.op_node_unstable_spawn_sync, + ), + serve: __bootstrap.flash.createServe(ops.op_node_unstable_flash_serve), + upgradeHttpRaw: __bootstrap.flash.upgradeHttpRaw, + listenDatagram: __bootstrap.net.createListenDatagram( + ops.op_node_unstable_net_listen_udp, + ops.op_node_unstable_net_listen_unixpacket, + ), + }, + }); + const finalDenoNs = { core, internal: internalSymbol, @@ -859,6 +916,19 @@ delete Intl.v8BreakIterator; }; if (runtimeOptions.unstableFlag) { ObjectAssign(finalDenoNs, denoNsUnstable); + // These have to initialized here and not in `90_deno_ns.js` because + // the op function that needs to be passed will be invalidated by creating + // a snapshot + ObjectAssign(finalDenoNs, { + spawnChild: __bootstrap.spawn.createSpawnChild(ops.op_spawn_child), + spawn: __bootstrap.spawn.createSpawn(ops.op_spawn_child), + spawnSync: __bootstrap.spawn.createSpawnSync(ops.op_spawn_sync), + serve: __bootstrap.flash.createServe(ops.op_flash_serve), + listenDatagram: __bootstrap.net.createListenDatagram( + ops.op_net_listen_udp, + ops.op_net_listen_unixpacket, + ), + }); } ObjectDefineProperties(finalDenoNs, { pid: util.readOnly(runtimeOptions.pid), diff --git a/runtime/ops/spawn.rs b/runtime/ops/spawn.rs index 7fe77302ae..03ab7d5c20 100644 --- a/runtime/ops/spawn.rs +++ b/runtime/ops/spawn.rs @@ -31,8 +31,10 @@ pub fn init() -> Extension { Extension::builder() .ops(vec![ op_spawn_child::decl(), + op_node_unstable_spawn_child::decl(), op_spawn_wait::decl(), op_spawn_sync::decl(), + op_node_unstable_spawn_sync::decl(), ]) .build() } @@ -123,6 +125,70 @@ pub struct SpawnOutput { stderr: Option, } +fn node_unstable_create_command( + state: &mut OpState, + args: SpawnArgs, + api_name: &str, +) -> Result { + state + .borrow_mut::() + .run + .check(&args.cmd, Some(api_name))?; + + let mut command = std::process::Command::new(args.cmd); + + #[cfg(windows)] + if args.windows_raw_arguments { + for arg in args.args.iter() { + command.raw_arg(arg); + } + } else { + command.args(args.args); + } + + #[cfg(not(windows))] + command.args(args.args); + + if let Some(cwd) = args.cwd { + command.current_dir(cwd); + } + + if args.clear_env { + command.env_clear(); + } + command.envs(args.env); + + #[cfg(unix)] + if let Some(gid) = args.gid { + command.gid(gid); + } + #[cfg(unix)] + if let Some(uid) = args.uid { + command.uid(uid); + } + #[cfg(unix)] + // TODO(bartlomieju): + #[allow(clippy::undocumented_unsafe_blocks)] + unsafe { + command.pre_exec(|| { + libc::setgroups(0, std::ptr::null()); + Ok(()) + }); + } + + command.stdin(args.stdio.stdin.as_stdio()); + command.stdout(match args.stdio.stdout { + Stdio::Inherit => StdioOrRid::Rid(1).as_stdio(state)?, + value => value.as_stdio(), + }); + command.stderr(match args.stdio.stderr { + Stdio::Inherit => StdioOrRid::Rid(2).as_stdio(state)?, + value => value.as_stdio(), + }); + + Ok(command) +} + fn create_command( state: &mut OpState, args: SpawnArgs, @@ -200,14 +266,11 @@ struct Child { stderr_rid: Option, } -#[op] -fn op_spawn_child( +fn spawn_child( state: &mut OpState, - args: SpawnArgs, - api_name: String, + command: std::process::Command, ) -> Result { - let mut command = - tokio::process::Command::from(create_command(state, args, &api_name)?); + let mut command = tokio::process::Command::from(command); // TODO(@crowlkats): allow detaching processes. // currently deno will orphan a process when exiting with an error or Deno.exit() // We want to kill child when it's closed @@ -242,6 +305,26 @@ fn op_spawn_child( }) } +#[op] +fn op_spawn_child( + state: &mut OpState, + args: SpawnArgs, + api_name: String, +) -> Result { + let command = create_command(state, args, &api_name)?; + spawn_child(state, command) +} + +#[op] +fn op_node_unstable_spawn_child( + state: &mut OpState, + args: SpawnArgs, + api_name: String, +) -> Result { + let command = node_unstable_create_command(state, args, &api_name)?; + spawn_child(state, command) +} + #[op] async fn op_spawn_wait( state: Rc>, @@ -283,3 +366,28 @@ fn op_spawn_sync( }, }) } + +#[op] +fn op_node_unstable_spawn_sync( + state: &mut OpState, + args: SpawnArgs, +) -> Result { + let stdout = matches!(args.stdio.stdout, Stdio::Piped); + let stderr = matches!(args.stdio.stderr, Stdio::Piped); + let output = + node_unstable_create_command(state, args, "Deno.spawnSync()")?.output()?; + + Ok(SpawnOutput { + status: output.status.try_into()?, + stdout: if stdout { + Some(output.stdout.into()) + } else { + None + }, + stderr: if stderr { + Some(output.stderr.into()) + } else { + None + }, + }) +} diff --git a/test_util/std b/test_util/std index 241e7eb8f9..c56a8c4e32 160000 --- a/test_util/std +++ b/test_util/std @@ -1 +1 @@ -Subproject commit 241e7eb8f91a06e75d8e7108b1b35ca202adf92f +Subproject commit c56a8c4e3245dd9ea1a892d2f2150ddba57f29c3