Repository: evanw/esbuild
File: lib/deno/mod.ts (TypeScript)

Code before:
  status(): Promise<{ code: number }>
}

// Deno ≥1.40
const spawnNew: SpawnFn = (cmd, { args, stdin, stdout, stderr }) => {
  const child = new Deno.Command(cmd, {
    args,
    cwd: defaultWD,
    stdin,
    stdout,
    stderr,
  }).spawn()
  const writer = child.stdin.getWriter()
  const reader = child.stdout.getReader()
  return {
    stdin: {
      write: bytes => writer.write(bytes),
      close: () => writer.close(),
    },
    stdout: {
      read: () => reader.read().then(x => x.value || null),
      close: () => reader.cancel(),
    },
    close: async () => {
      // Note: This can throw with EPERM on Windows (happens in GitHub Actions)
      child.kill()

      // Without this, Deno will fail tests with some weird error about "op_spawn_wait"
      await child.status
    },
    status: () => child.status,
  }
}

// Deno <1.40
const spawnOld: SpawnFn = (cmd, { args, stdin, stdout, stderr }) => {
  const child = Deno.run({
    cmd: [cmd].concat(args),
    cwd: defaultWD,
    stdin,
    stdout,
    stderr,
  })
  const stdoutBuffer = new Uint8Array(4 * 1024 * 1024)
  let writeQueue: Uint8Array[] = []
  let isQueueLocked = false

  // We need to keep calling "write()" until it actually writes the data
  const startWriteFromQueueWorker = () => {
    if (isQueueLocked || writeQueue.length === 0) return
    isQueueLocked = true

Review comment (bug):
Trying to kill the child process on Windows throws `PermissionDenied: Access is denied. (os error 5)`. But not killing the process on Windows causes Deno to fail tests with `1 async operation to op_spawn_wait was started in this test, but never completed`. So it seems like maybe there's no way to get tests to pass on Windows with Deno's new APIs? I'm not sure how to proceed here. Maybe this is a bug in Deno itself?


Code after:
const spawnNew: SpawnFn = (cmd, { args, stdin, stdout, stderr }) => {
  const child = new Deno.Command(cmd, {
    args,
    cwd: defaultWD,
    stdin,
    stdout,
    stderr,
  }).spawn()
  const writer = child.stdin.getWriter()
  const reader = child.stdout.getReader()
  return {
    write: bytes => writer.write(bytes),
    read: () => reader.read().then(x => x.value || null),
    close: async () => {
      // We can't call "kill()" because it doesn't seem to work. Tests will
      // still fail with "A child process was opened during the test, but not
      // closed during the test" even though we kill the child process.
      //
      // And we can't call both "writer.close()" and "kill()" because then
      // there's a race as the child process exits when stdin is closed, and
      // "kill()" fails when the child process has already been killed.
      //
      // So instead we just call "writer.close()" and then hope that this
      // causes the child process to exit. It won't work if the stdin consumer
      // thread in the child process is hung or busy, but that may be the best
      // we can do.
      //
      // See this for more info: https://github.com/evanw/esbuild/pull/3611
      await writer.close()
      await reader.cancel()

      // Wait for the process to exit. The new "kill()" API doesn't flag the
      // process as having exited because processes can technically ignore the
      // kill signal. Without this, Deno will fail tests that use esbuild with
      // an error because the test spawned a process but didn't wait for it.
      await child.status
    },
    status: () => child.status,
    unref: () => child.unref(),
    ref: () => child.ref(),
  }
}

// Deno <1.40
const spawnOld: SpawnFn = (cmd, { args, stdin, stdout, stderr }) => {
  const child = Deno.run({
    cmd: [cmd].concat(args),
    cwd: defaultWD,
    stdin,
    stdout,
    stderr,