From cb6700fa5aac03fb3e082f9ed2e01d74238e6a99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 9 Dec 2022 16:43:36 +0100 Subject: [PATCH] unstable: remove Deno.spawn, Deno.spawnSync, Deno.spawnChild APIs (#16893) This commit removes three unstable Deno APIs: - "Deno.spawn()" - "Deno.spawnSync()" - "Deno.spawnChild()" These APIs were replaced by a unified "Deno.Command" API. --- cli/bench/{spawn.js => command.js} | 6 +- cli/tests/unit/spawn_test.ts | 831 ----------------------------- cli/tsc/dts/lib.deno.unstable.d.ts | 164 ------ runtime/js/40_spawn.js | 9 +- runtime/js/99_main.js | 75 +-- runtime/ops/spawn.rs | 6 +- 6 files changed, 38 insertions(+), 1053 deletions(-) rename cli/bench/{spawn.js => command.js} (64%) delete mode 100644 cli/tests/unit/spawn_test.ts diff --git a/cli/bench/spawn.js b/cli/bench/command.js similarity index 64% rename from cli/bench/spawn.js rename to cli/bench/command.js index f5a658f3f1..5b57dd4f67 100644 --- a/cli/bench/spawn.js +++ b/cli/bench/command.js @@ -1,11 +1,11 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. Deno.bench("echo deno", async () => { - await Deno.spawn("echo", { args: ["deno"] }); + await new Deno.Command("echo", { args: ["deno"] }).output(); }); Deno.bench("cat 128kb", async () => { - await Deno.spawn("cat", { + await new Deno.Command("cat", { args: ["./cli/bench/testdata/128k.bin"], - }); + }).output(); }); diff --git a/cli/tests/unit/spawn_test.ts b/cli/tests/unit/spawn_test.ts deleted file mode 100644 index 10095be95e..0000000000 --- a/cli/tests/unit/spawn_test.ts +++ /dev/null @@ -1,831 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -import { - assert, - assertEquals, - assertRejects, - assertStringIncludes, - assertThrows, -} from "./test_util.ts"; - -Deno.test( - { permissions: { write: true, run: true, read: true } }, - async function spawnWithCwdIsAsync() { - const enc = new TextEncoder(); - const cwd = await Deno.makeTempDir({ prefix: "deno_command_test" }); - - const exitCodeFile = "deno_was_here"; - const programFile = "poll_exit.ts"; - const program = ` -async function tryExit() { - try { - const code = parseInt(await Deno.readTextFile("${exitCodeFile}")); - Deno.exit(code); - } catch { - // Retry if we got here before deno wrote the file. - setTimeout(tryExit, 0.01); - } -} - -tryExit(); -`; - - Deno.writeFileSync(`${cwd}/${programFile}`, enc.encode(program)); - - const child = Deno.spawnChild(Deno.execPath(), { - cwd, - args: ["run", "--allow-read", programFile], - stdout: "inherit", - stderr: "inherit", - }); - - // Write the expected exit code *after* starting deno. - // This is how we verify that `Child` is actually asynchronous. - const code = 84; - Deno.writeFileSync(`${cwd}/${exitCodeFile}`, enc.encode(`${code}`)); - - const status = await child.status; - await Deno.remove(cwd, { recursive: true }); - assertEquals(status.success, false); - assertEquals(status.code, code); - assertEquals(status.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnStdinPiped() { - const child = Deno.spawnChild(Deno.execPath(), { - args: [ - "eval", - "if (new TextDecoder().decode(await Deno.readAll(Deno.stdin)) !== 'hello') throw new Error('Expected \\'hello\\'')", - ], - stdin: "piped", - stdout: "null", - stderr: "null", - }); - - assertThrows(() => child.stdout, TypeError, "stdout is not piped"); - assertThrows(() => child.stderr, TypeError, "stderr is not piped"); - - const msg = new TextEncoder().encode("hello"); - const writer = child.stdin.getWriter(); - await writer.write(msg); - writer.releaseLock(); - - await child.stdin.close(); - const status = await child.status; - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnStdoutPiped() { - const child = Deno.spawnChild(Deno.execPath(), { - args: [ - "eval", - "await Deno.stdout.write(new TextEncoder().encode('hello'))", - ], - stderr: "null", - }); - - assertThrows(() => child.stdin, TypeError, "stdin is not piped"); - assertThrows(() => child.stderr, TypeError, "stderr is not piped"); - - const readable = child.stdout.pipeThrough(new TextDecoderStream()); - const reader = readable.getReader(); - const res = await reader.read(); - assert(!res.done); - assertEquals(res.value, "hello"); - - const resEnd = await reader.read(); - assert(resEnd.done); - assertEquals(resEnd.value, undefined); - reader.releaseLock(); - - const status = await child.status; - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnStderrPiped() { - const child = Deno.spawnChild(Deno.execPath(), { - args: [ - "eval", - "await Deno.stderr.write(new TextEncoder().encode('hello'))", - ], - stdout: "null", - }); - - assertThrows(() => child.stdin, TypeError, "stdin is not piped"); - assertThrows(() => child.stdout, TypeError, "stdout is not piped"); - - const readable = child.stderr.pipeThrough(new TextDecoderStream()); - const reader = readable.getReader(); - const res = await reader.read(); - assert(!res.done); - assertEquals(res.value, "hello"); - - const resEnd = await reader.read(); - assert(resEnd.done); - assertEquals(resEnd.value, undefined); - reader.releaseLock(); - - const status = await child.status; - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, write: true, read: true } }, - async function spawnRedirectStdoutStderr() { - const tempDir = await Deno.makeTempDir(); - const fileName = tempDir + "/redirected_stdio.txt"; - const file = await Deno.open(fileName, { - create: true, - write: true, - }); - - const child = Deno.spawnChild(Deno.execPath(), { - args: [ - "eval", - "Deno.stderr.write(new TextEncoder().encode('error\\n')); Deno.stdout.write(new TextEncoder().encode('output\\n'));", - ], - }); - await child.stdout.pipeTo(file.writable, { - preventClose: true, - }); - await child.stderr.pipeTo(file.writable); - await child.status; - - const fileContents = await Deno.readFile(fileName); - const decoder = new TextDecoder(); - const text = decoder.decode(fileContents); - - assertStringIncludes(text, "error"); - assertStringIncludes(text, "output"); - }, -); - -Deno.test( - { permissions: { run: true, write: true, read: true } }, - async function spawnRedirectStdin() { - const tempDir = await Deno.makeTempDir(); - const fileName = tempDir + "/redirected_stdio.txt"; - const encoder = new TextEncoder(); - await Deno.writeFile(fileName, encoder.encode("hello")); - const file = await Deno.open(fileName); - - const child = Deno.spawnChild(Deno.execPath(), { - args: [ - "eval", - "if (new TextDecoder().decode(await Deno.readAll(Deno.stdin)) !== 'hello') throw new Error('Expected \\'hello\\'')", - ], - stdin: "piped", - stdout: "null", - stderr: "null", - }); - await file.readable.pipeTo(child.stdin, { - preventClose: true, - }); - - await child.stdin.close(); - const status = await child.status; - assertEquals(status.code, 0); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnKillSuccess() { - const child = Deno.spawnChild(Deno.execPath(), { - args: ["eval", "setTimeout(() => {}, 10000)"], - stdout: "null", - stderr: "null", - }); - - child.kill("SIGKILL"); - const status = await child.status; - - assertEquals(status.success, false); - if (Deno.build.os === "windows") { - assertEquals(status.code, 1); - assertEquals(status.signal, null); - } else { - assertEquals(status.code, 137); - assertEquals(status.signal, "SIGKILL"); - } - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnKillFailed() { - const child = Deno.spawnChild(Deno.execPath(), { - args: ["eval", "setTimeout(() => {}, 5000)"], - stdout: "null", - stderr: "null", - }); - - assertThrows(() => { - // @ts-expect-error testing runtime error of bad signal - child.kill("foobar"); - }, TypeError); - - await child.status; - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnKillOptional() { - const child = Deno.spawnChild(Deno.execPath(), { - args: ["eval", "setTimeout(() => {}, 10000)"], - stdout: "null", - stderr: "null", - }); - - child.kill(); - const status = await child.status; - - assertEquals(status.success, false); - if (Deno.build.os === "windows") { - assertEquals(status.code, 1); - assertEquals(status.signal, null); - } else { - assertEquals(status.code, 143); - assertEquals(status.signal, "SIGTERM"); - } - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnAbort() { - const ac = new AbortController(); - const child = Deno.spawnChild(Deno.execPath(), { - args: [ - "eval", - "setTimeout(console.log, 1e8)", - ], - signal: ac.signal, - stdout: "null", - stderr: "null", - }); - queueMicrotask(() => ac.abort()); - const status = await child.status; - assertEquals(status.success, false); - if (Deno.build.os === "windows") { - assertEquals(status.code, 1); - assertEquals(status.signal, null); - } else { - assertEquals(status.success, false); - assertEquals(status.code, 143); - } - }, -); - -Deno.test( - { permissions: { read: true, run: false } }, - async function spawnPermissions() { - await assertRejects(async () => { - await Deno.spawn(Deno.execPath(), { - args: ["eval", "console.log('hello world')"], - }); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { read: true, run: false } }, - function spawnSyncPermissions() { - assertThrows(() => { - Deno.spawnSync(Deno.execPath(), { - args: ["eval", "console.log('hello world')"], - }); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnSuccess() { - const output = await Deno.spawn(Deno.execPath(), { - args: ["eval", "console.log('hello world')"], - }); - - assertEquals(output.success, true); - assertEquals(output.code, 0); - assertEquals(output.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function spawnSyncSuccess() { - const output = Deno.spawnSync(Deno.execPath(), { - args: ["eval", "console.log('hello world')"], - }); - - assertEquals(output.success, true); - assertEquals(output.code, 0); - assertEquals(output.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnUrl() { - const output = await Deno.spawn( - new URL(`file:///${Deno.execPath()}`), - { - args: ["eval", "console.log('hello world')"], - }, - ); - - assertEquals(new TextDecoder().decode(output.stdout), "hello world\n"); - - assertEquals(output.success, true); - assertEquals(output.code, 0); - assertEquals(output.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function spawnSyncUrl() { - const output = Deno.spawnSync( - new URL(`file:///${Deno.execPath()}`), - { - args: ["eval", "console.log('hello world')"], - }, - ); - - assertEquals(new TextDecoder().decode(output.stdout), "hello world\n"); - - assertEquals(output.success, true); - assertEquals(output.code, 0); - assertEquals(output.signal, null); - }, -); - -Deno.test({ permissions: { run: true } }, function spawnNotFound() { - assertThrows( - () => Deno.spawn("this file hopefully doesn't exist"), - Deno.errors.NotFound, - ); -}); - -Deno.test({ permissions: { run: true } }, function spawnSyncNotFound() { - assertThrows( - () => Deno.spawnSync("this file hopefully doesn't exist"), - Deno.errors.NotFound, - ); -}); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnFailedWithCode() { - const output = await Deno.spawn(Deno.execPath(), { - args: ["eval", "Deno.exit(41 + 1)"], - }); - assertEquals(output.success, false); - assertEquals(output.code, 42); - assertEquals(output.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function spawnSyncFailedWithCode() { - const output = Deno.spawnSync(Deno.execPath(), { - args: ["eval", "Deno.exit(41 + 1)"], - }); - assertEquals(output.success, false); - assertEquals(output.code, 42); - assertEquals(output.signal, null); - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - }, - async function spawnFailedWithSignal() { - const output = await Deno.spawn(Deno.execPath(), { - args: ["eval", "--unstable", "Deno.kill(Deno.pid, 'SIGKILL')"], - }); - assertEquals(output.success, false); - if (Deno.build.os === "windows") { - assertEquals(output.code, 1); - assertEquals(output.signal, null); - } else { - assertEquals(output.code, 128 + 9); - assertEquals(output.signal, "SIGKILL"); - } - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - }, - function spawnSyncFailedWithSignal() { - const output = Deno.spawnSync(Deno.execPath(), { - args: ["eval", "--unstable", "Deno.kill(Deno.pid, 'SIGKILL')"], - }); - assertEquals(output.success, false); - if (Deno.build.os === "windows") { - assertEquals(output.code, 1); - assertEquals(output.signal, null); - } else { - assertEquals(output.code, 128 + 9); - assertEquals(output.signal, "SIGKILL"); - } - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnOutput() { - const { stdout } = await Deno.spawn(Deno.execPath(), { - args: [ - "eval", - "await Deno.stdout.write(new TextEncoder().encode('hello'))", - ], - }); - - const s = new TextDecoder().decode(stdout); - assertEquals(s, "hello"); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function spawnSyncOutput() { - const { stdout } = Deno.spawnSync(Deno.execPath(), { - args: [ - "eval", - "await Deno.stdout.write(new TextEncoder().encode('hello'))", - ], - }); - - const s = new TextDecoder().decode(stdout); - assertEquals(s, "hello"); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnStderrOutput() { - const { stderr } = await Deno.spawn(Deno.execPath(), { - args: [ - "eval", - "await Deno.stderr.write(new TextEncoder().encode('error'))", - ], - }); - - const s = new TextDecoder().decode(stderr); - assertEquals(s, "error"); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function spawnSyncStderrOutput() { - const { stderr } = Deno.spawnSync(Deno.execPath(), { - args: [ - "eval", - "await Deno.stderr.write(new TextEncoder().encode('error'))", - ], - }); - - const s = new TextDecoder().decode(stderr); - assertEquals(s, "error"); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnEnv() { - const { stdout } = await Deno.spawn(Deno.execPath(), { - args: [ - "eval", - "Deno.stdout.write(new TextEncoder().encode(Deno.env.get('FOO') + Deno.env.get('BAR')))", - ], - env: { - FOO: "0123", - BAR: "4567", - }, - }); - const s = new TextDecoder().decode(stdout); - assertEquals(s, "01234567"); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function spawnSyncEnv() { - const { stdout } = Deno.spawnSync(Deno.execPath(), { - args: [ - "eval", - "Deno.stdout.write(new TextEncoder().encode(Deno.env.get('FOO') + Deno.env.get('BAR')))", - ], - env: { - FOO: "0123", - BAR: "4567", - }, - }); - const s = new TextDecoder().decode(stdout); - assertEquals(s, "01234567"); - }, -); - -Deno.test( - { permissions: { run: true, read: true, env: true } }, - async function spawnClearEnv() { - const { stdout } = await Deno.spawn(Deno.execPath(), { - args: [ - "eval", - "-p", - "JSON.stringify(Deno.env.toObject())", - ], - clearEnv: true, - env: { - FOO: "23147", - }, - }); - - const obj = JSON.parse(new TextDecoder().decode(stdout)); - - // can't check for object equality because the OS may set additional env - // vars for processes, so we check if PATH isn't present as that is a common - // env var across OS's and isn't set for processes. - assertEquals(obj.FOO, "23147"); - assert(!("PATH" in obj)); - }, -); - -Deno.test( - { permissions: { run: true, read: true, env: true } }, - function spawnSyncClearEnv() { - const { stdout } = Deno.spawnSync(Deno.execPath(), { - args: [ - "eval", - "-p", - "JSON.stringify(Deno.env.toObject())", - ], - clearEnv: true, - env: { - FOO: "23147", - }, - }); - - const obj = JSON.parse(new TextDecoder().decode(stdout)); - - // can't check for object equality because the OS may set additional env - // vars for processes, so we check if PATH isn't present as that is a common - // env var across OS's and isn't set for processes. - assertEquals(obj.FOO, "23147"); - assert(!("PATH" in obj)); - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - ignore: Deno.build.os === "windows", - }, - async function spawnUid() { - const { stdout } = await Deno.spawn("id", { - args: ["-u"], - }); - - const currentUid = new TextDecoder().decode(stdout); - - if (currentUid !== "0") { - await assertRejects(async () => { - await Deno.spawn("echo", { - args: ["fhqwhgads"], - uid: 0, - }); - }, Deno.errors.PermissionDenied); - } - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - ignore: Deno.build.os === "windows", - }, - function spawnSyncUid() { - const { stdout } = Deno.spawnSync("id", { - args: ["-u"], - }); - - const currentUid = new TextDecoder().decode(stdout); - - if (currentUid !== "0") { - assertThrows(() => { - Deno.spawnSync("echo", { - args: ["fhqwhgads"], - uid: 0, - }); - }, Deno.errors.PermissionDenied); - } - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - ignore: Deno.build.os === "windows", - }, - async function spawnGid() { - const { stdout } = await Deno.spawn("id", { - args: ["-g"], - }); - - const currentGid = new TextDecoder().decode(stdout); - - if (currentGid !== "0") { - await assertRejects(async () => { - await Deno.spawn("echo", { - args: ["fhqwhgads"], - gid: 0, - }); - }, Deno.errors.PermissionDenied); - } - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - ignore: Deno.build.os === "windows", - }, - function spawnSyncGid() { - const { stdout } = Deno.spawnSync("id", { - args: ["-g"], - }); - - const currentGid = new TextDecoder().decode(stdout); - - if (currentGid !== "0") { - assertThrows(() => { - Deno.spawnSync("echo", { - args: ["fhqwhgads"], - gid: 0, - }); - }, Deno.errors.PermissionDenied); - } - }, -); - -Deno.test(function spawnStdinPipedFails() { - assertThrows( - () => - Deno.spawn("id", { - stdin: "piped", - }), - TypeError, - "Piped stdin is not supported for this function, use 'Deno.spawnChild()' instead", - ); -}); - -Deno.test(function spawnSyncStdinPipedFails() { - assertThrows( - () => - Deno.spawnSync("id", { - stdin: "piped", - }), - TypeError, - "Piped stdin is not supported for this function, use 'Deno.spawnChild()' instead", - ); -}); - -Deno.test( - // TODO(bartlomieju): this test became flaky on Windows CI - // raising "PermissionDenied" instead of "NotFound". - { - ignore: Deno.build.os === "windows", - permissions: { write: true, run: true, read: true }, - }, - async function spawnChildUnref() { - const enc = new TextEncoder(); - const cwd = await Deno.makeTempDir({ prefix: "deno_command_test" }); - - const programFile = "unref.ts"; - const program = ` -const child = await Deno.spawnChild(Deno.execPath(), { - cwd: Deno.args[0], - stdout: "piped", - args: ["run", "-A", "--unstable", Deno.args[1]], -}); -const readable = child.stdout.pipeThrough(new TextDecoderStream()); -const reader = readable.getReader(); -// set up an interval that will end after reading a few messages from stdout, -// to verify that stdio streams are properly unrefed -let count = 0; -let interval; -interval = setInterval(async () => { - count += 1; - if (count > 10) { - clearInterval(interval); - console.log("cleared interval"); - } - const res = await reader.read(); - if (res.done) { - throw new Error("stream shouldn't be done"); - } - if (res.value.trim() != "hello from interval") { - throw new Error("invalid message received"); - } -}, 120); -console.log("spawned pid", child.pid); -child.unref(); -`; - - const childProgramFile = "unref_child.ts"; - const childProgram = ` -setInterval(() => { - console.log("hello from interval"); -}, 100); -`; - Deno.writeFileSync(`${cwd}/${programFile}`, enc.encode(program)); - Deno.writeFileSync(`${cwd}/${childProgramFile}`, enc.encode(childProgram)); - // In this subprocess we are spawning another subprocess which has - // an infite interval set. Following call would never resolve unless - // child process gets unrefed. - const { success, stdout, stderr } = await Deno.spawn(Deno.execPath(), { - cwd, - args: ["run", "-A", "--unstable", programFile, cwd, childProgramFile], - }); - - assert(success); - const stdoutText = new TextDecoder().decode(stdout); - const stderrText = new TextDecoder().decode(stderr); - assert(stderrText.length == 0); - const [line1, line2] = stdoutText.split("\n"); - const pidStr = line1.split(" ").at(-1); - assert(pidStr); - assertEquals(line2, "cleared interval"); - const pid = Number.parseInt(pidStr, 10); - await Deno.remove(cwd, { recursive: true }); - // Child process should have been killed when parent process exits. - assertThrows(() => { - Deno.kill(pid, "SIGTERM"); - }, Deno.errors.NotFound); - }, -); - -Deno.test( - { ignore: Deno.build.os !== "windows" }, - async function spawnWindowsRawArguments() { - let { success, stdout } = await Deno.spawn("cmd", { - args: ["/d", "/s", "/c", '"deno ^"--version^""'], - windowsRawArguments: true, - }); - assert(success); - let stdoutText = new TextDecoder().decode(stdout); - assertStringIncludes(stdoutText, "deno"); - assertStringIncludes(stdoutText, "v8"); - assertStringIncludes(stdoutText, "typescript"); - - ({ success, stdout } = Deno.spawnSync("cmd", { - args: ["/d", "/s", "/c", '"deno ^"--version^""'], - windowsRawArguments: true, - })); - assert(success); - stdoutText = new TextDecoder().decode(stdout); - assertStringIncludes(stdoutText, "deno"); - assertStringIncludes(stdoutText, "v8"); - assertStringIncludes(stdoutText, "typescript"); - }, -); - -Deno.test( - { permissions: { read: true, run: true } }, - async function spawnWithPromisePrototypeThenOverride() { - const originalThen = Promise.prototype.then; - try { - Promise.prototype.then = () => { - throw new Error(); - }; - await Deno.spawn(Deno.execPath(), { - args: ["eval", "console.log('hello world')"], - }); - } finally { - Promise.prototype.then = originalThen; - } - }, -); diff --git a/cli/tsc/dts/lib.deno.unstable.d.ts b/cli/tsc/dts/lib.deno.unstable.d.ts index 9e91c88004..d3bf8a91b0 100644 --- a/cli/tsc/dts/lib.deno.unstable.d.ts +++ b/cli/tsc/dts/lib.deno.unstable.d.ts @@ -1468,170 +1468,6 @@ declare namespace Deno { windowsRawArguments?: boolean; } - /** **UNSTABLE**: New API, yet to be vetted. - * - * @deprecated Use the Deno.Command API instead. - * - * Spawns a child process. - * - * If any stdio options are not set to `"piped"`, accessing the corresponding - * field on the `Child` or its `SpawnOutput` will throw a `TypeError`. - * - * If `stdin` is set to `"piped"`, the `stdin` {@linkcode WritableStream} - * needs to be closed manually. - * - * ```ts - * const child = Deno.spawnChild(Deno.execPath(), { - * args: [ - * "eval", - * "console.log('Hello World')", - * ], - * stdin: "piped", - * }); - * - * // open a file and pipe the subprocess output to it. - * child.stdout.pipeTo(Deno.openSync("output").writable); - * - * // manually close stdin - * child.stdin.close(); - * const status = await child.status; - * ``` - * - * @category Sub Process - */ - export function spawnChild( - command: string | URL, - options?: SpawnOptions, - ): Child; - - /** **UNSTABLE**: New API, yet to be vetted. - * - * @deprecated Use the Deno.Command API instead. - * - * The interface for handling a child process returned from - * {@linkcode Deno.spawnChild}. - * - * @category Sub Process - */ - export class Child { - get stdin(): WritableStream; - get stdout(): ReadableStream; - get stderr(): ReadableStream; - readonly pid: number; - /** Get the status of the child. */ - readonly status: Promise; - - /** Waits for the child to exit completely, returning all its output and - * status. */ - output(): Promise; - /** Kills the process with given {@linkcode Deno.Signal}. Defaults to - * `"SIGTERM"`. */ - kill(signo?: Signal): void; - - /** Ensure that the status of the child process prevents the Deno process - * from exiting. */ - ref(): void; - /** Ensure that the status of the child process does not block the Deno - * process from exiting. */ - unref(): void; - } - - /** **UNSTABLE**: New API, yet to be vetted. - * - * @deprecated Use the Deno.Command API instead. - * - * Executes a subprocess, waiting for it to finish and collecting all of its - * output. - * - * Will throw an error if `stdin: "piped"` is passed. - * - * If options `stdout` or `stderr` are not set to `"piped"`, accessing the - * corresponding field on `SpawnOutput` will throw a `TypeError`. - * - * ```ts - * const { code, stdout, stderr } = await Deno.spawn(Deno.execPath(), { - * args: [ - * "eval", - * "console.log('hello'); console.error('world')", - * ], - * }); - * console.assert(code === 0); - * console.assert("hello\n" === new TextDecoder().decode(stdout)); - * console.assert("world\n" === new TextDecoder().decode(stderr)); - * ``` - * - * @category Sub Process - */ - export function spawn( - command: string | URL, - options?: SpawnOptions, - ): Promise; - - /** **UNSTABLE**: New API, yet to be vetted. - * - * @deprecated Use the Deno.Command API instead. - * - * Synchronously executes a subprocess, waiting for it to finish and - * collecting all of its output. - * - * Will throw an error if `stdin: "piped"` is passed. - * - * If options `stdout` or `stderr` are not set to `"piped"`, accessing the - * corresponding field on `SpawnOutput` will throw a `TypeError`. - * - * ```ts - * const { code, stdout, stderr } = Deno.spawnSync(Deno.execPath(), { - * args: [ - * "eval", - * "console.log('hello'); console.error('world')", - * ], - * }); - * console.assert(code === 0); - * console.assert("hello\n" === new TextDecoder().decode(stdout)); - * console.assert("world\n" === new TextDecoder().decode(stderr)); - * ``` - * - * @category Sub Process - */ - export function spawnSync( - command: string | URL, - options?: SpawnOptions, - ): SpawnOutput; - - /** **UNSTABLE**: New API, yet to be vetted. - * - * @deprecated Use the Deno.Command API instead. - * - * @category Sub Process - */ - export interface ChildStatus { - /** If the child process exits with a 0 status code, `success` will be set - * to `true`, otherwise `false`. */ - success: boolean; - /** The exit code of the child process. */ - code: number; - /** The signal associated with the child process, present if - * {@linkcode Deno.spawn} was called. */ - signal: Signal | null; - } - - /** **UNSTABLE**: New API, yet to be vetted. - * - * @deprecated Use the Deno.Command API instead. - * - * The interface returned from calling {@linkcode Deno.spawn} or - * {@linkcode Deno.spawnSync} which represents the result of spawning the - * child process. - * - * @category Sub Process - */ - export interface SpawnOutput extends ChildStatus { - /** The buffered output from the child processes `stdout`. */ - readonly stdout: Uint8Array; - /** The buffered output from the child processes `stderr`. */ - readonly stderr: Uint8Array; - } - /** **UNSTABLE**: New API, yet to be vetted. * * Create a child process. diff --git a/runtime/js/40_spawn.js b/runtime/js/40_spawn.js index 863063e3f6..ea6b409a37 100644 --- a/runtime/js/40_spawn.js +++ b/runtime/js/40_spawn.js @@ -60,7 +60,7 @@ function createSpawnChild(opFn) { return function spawnChild(command, options = {}) { - return spawnChildInner(opFn, command, "Deno.spawnChild()", options); + return spawnChildInner(opFn, command, "Deno.Command().spawn()", options); }; } @@ -219,10 +219,11 @@ return function spawn(command, options) { if (options?.stdin === "piped") { throw new TypeError( - "Piped stdin is not supported for this function, use 'Deno.spawnChild()' instead", + "Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead", ); } - return spawnChildInner(opFn, command, "Deno.spawn()", options).output(); + return spawnChildInner(opFn, command, "Deno.Command().output()", options) + .output(); }; } @@ -241,7 +242,7 @@ } = {}) { if (stdin === "piped") { throw new TypeError( - "Piped stdin is not supported for this function, use 'Deno.spawnChild()' instead", + "Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead", ); } const result = opFn({ diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index adfc0d3602..1eb71339fa 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -466,12 +466,14 @@ delete Intl.v8BreakIterator; // 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, + Command: __bootstrap.spawn.createCommand( + __bootstrap.spawn.createSpawn(ops.op_node_unstable_spawn_child), + __bootstrap.spawn.createSpawnSync( + ops.op_node_unstable_spawn_sync, + ), + __bootstrap.spawn.createSpawnChild( + ops.op_node_unstable_spawn_child, + ), ), serve: __bootstrap.flash.createServe(ops.op_node_unstable_flash_serve), upgradeHttpRaw: __bootstrap.flash.upgradeHttpRaw, @@ -482,14 +484,6 @@ delete Intl.v8BreakIterator; }, }); - ObjectAssign(internals.nodeUnstable, { - Command: __bootstrap.spawn.createCommand( - internals.nodeUnstable.spawn, - internals.nodeUnstable.spawnSync, - internals.nodeUnstable.spawnChild, - ), - }); - const finalDenoNs = { core, internal: internalSymbol, @@ -512,23 +506,17 @@ delete Intl.v8BreakIterator; // 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), + Command: __bootstrap.spawn.createCommand( + __bootstrap.spawn.createSpawn(ops.op_spawn_child), + __bootstrap.spawn.createSpawnSync(ops.op_spawn_sync), + __bootstrap.spawn.createSpawnChild(ops.op_spawn_child), + ), serve: __bootstrap.flash.createServe(ops.op_flash_serve), listenDatagram: __bootstrap.net.createListenDatagram( ops.op_net_listen_udp, ops.op_net_listen_unixpacket, ), }); - - ObjectAssign(finalDenoNs, { - Command: __bootstrap.spawn.createCommand( - finalDenoNs.spawn, - finalDenoNs.spawnSync, - finalDenoNs.spawnChild, - ), - }); } // Setup `Deno` global - we're actually overriding already existing global @@ -617,12 +605,14 @@ delete Intl.v8BreakIterator; // 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, + Command: __bootstrap.spawn.createCommand( + __bootstrap.spawn.createSpawn(ops.op_node_unstable_spawn_child), + __bootstrap.spawn.createSpawnSync( + ops.op_node_unstable_spawn_sync, + ), + __bootstrap.spawn.createSpawnChild( + ops.op_node_unstable_spawn_child, + ), ), serve: __bootstrap.flash.createServe(ops.op_node_unstable_flash_serve), upgradeHttpRaw: __bootstrap.flash.upgradeHttpRaw, @@ -633,14 +623,6 @@ delete Intl.v8BreakIterator; }, }); - ObjectAssign(internals.nodeUnstable, { - Command: __bootstrap.spawn.createCommand( - internals.nodeUnstable.spawn, - internals.nodeUnstable.spawnSync, - internals.nodeUnstable.spawnChild, - ), - }); - const finalDenoNs = { core, internal: internalSymbol, @@ -655,22 +637,17 @@ delete Intl.v8BreakIterator; // 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), + Command: __bootstrap.spawn.createCommand( + __bootstrap.spawn.createSpawn(ops.op_spawn_child), + __bootstrap.spawn.createSpawnSync(ops.op_spawn_sync), + __bootstrap.spawn.createSpawnChild(ops.op_spawn_child), + ), serve: __bootstrap.flash.createServe(ops.op_flash_serve), listenDatagram: __bootstrap.net.createListenDatagram( ops.op_net_listen_udp, ops.op_net_listen_unixpacket, ), }); - ObjectAssign(finalDenoNs, { - Command: __bootstrap.spawn.createCommand( - finalDenoNs.spawn, - finalDenoNs.spawnSync, - finalDenoNs.spawnChild, - ), - }); } ObjectDefineProperties(finalDenoNs, { pid: util.readOnly(runtimeOptions.pid), diff --git a/runtime/ops/spawn.rs b/runtime/ops/spawn.rs index 03ab7d5c20..11940013c5 100644 --- a/runtime/ops/spawn.rs +++ b/runtime/ops/spawn.rs @@ -350,7 +350,8 @@ fn op_spawn_sync( ) -> Result { let stdout = matches!(args.stdio.stdout, Stdio::Piped); let stderr = matches!(args.stdio.stderr, Stdio::Piped); - let output = create_command(state, args, "Deno.spawnSync()")?.output()?; + let output = + create_command(state, args, "Deno.Command().outputSync()")?.output()?; Ok(SpawnOutput { status: output.status.try_into()?, @@ -375,7 +376,8 @@ fn op_node_unstable_spawn_sync( 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()?; + node_unstable_create_command(state, args, "Deno.Command().outputSync()")? + .output()?; Ok(SpawnOutput { status: output.status.try_into()?,