diff --git a/async/unstable_wait_for.ts b/async/unstable_wait_for.ts index ca71dafd638e..d776342b035c 100644 --- a/async/unstable_wait_for.ts +++ b/async/unstable_wait_for.ts @@ -1,4 +1,4 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// Copyright 2018-2025 the Deno authors. MIT license. // This module is browser compatible. import { deadline } from "./deadline.ts"; diff --git a/async/unstable_wait_for_test.ts b/async/unstable_wait_for_test.ts index 2c286413d074..d8b3156ebed7 100644 --- a/async/unstable_wait_for_test.ts +++ b/async/unstable_wait_for_test.ts @@ -1,4 +1,4 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// Copyright 2018-2025 the Deno authors. MIT license. import { assertAlmostEquals, assertEquals, assertRejects } from "@std/assert"; import { waitFor } from "./unstable_wait_for.ts"; diff --git a/cli/unstable_spinner.ts b/cli/unstable_spinner.ts index 178a5b14965c..e76c4faaa4cc 100644 --- a/cli/unstable_spinner.ts +++ b/cli/unstable_spinner.ts @@ -75,6 +75,12 @@ export interface SpinnerOptions { * This can be changed while the spinner is active. */ color?: Color; + /** + * The stream to write the spinner to. + * + * @default {Deno.stdout} + */ + stream?: typeof Deno.stderr | typeof Deno.stdout; } /** @@ -93,6 +99,15 @@ export interface SpinnerOptions { * spinner.stop(); * console.log("Finished loading!"); * }, 3_000); + * + * // You can also use the spinner with `Deno.stderr` + * const spinner2 = new Spinner({ message: "Loading...", color: "yellow", stream: Deno.stderr }); + * spinner.start(); + * + * setTimeout(() => { + * spinner.stop(); + * console.error"Finished loading!"); + * }, 3_000); * ``` */ export class Spinner { @@ -126,6 +141,7 @@ export class Spinner { #color: Color | undefined; #intervalId: number | undefined; #active = false; + #stream: typeof Deno.stdout | typeof Deno.stderr; /** * Creates a new spinner. @@ -142,6 +158,7 @@ export class Spinner { this.#spinner = spinner; this.message = message; this.#interval = interval; + this.#stream = options?.stream ?? Deno.stdout; this.color = color; } @@ -199,7 +216,7 @@ export class Spinner { * ``` */ start() { - if (this.#active || Deno.stdout.writable.locked) { + if (this.#active || this.#stream.writable.locked) { return; } @@ -219,7 +236,7 @@ export class Spinner { const writeData = new Uint8Array(LINE_CLEAR.length + frame.length); writeData.set(LINE_CLEAR); writeData.set(frame, LINE_CLEAR.length); - Deno.stdout.writeSync(writeData); + this.#stream.writeSync(writeData); i = (i + 1) % this.#spinner.length; }; @@ -245,7 +262,7 @@ export class Spinner { stop() { if (this.#intervalId && this.#active) { clearInterval(this.#intervalId); - Deno.stdout.writeSync(LINE_CLEAR); // Clear the current line + this.#stream.writeSync(LINE_CLEAR); // Clear the current line this.#active = false; } } diff --git a/cli/unstable_spinner_test.ts b/cli/unstable_spinner_test.ts index a1833c2cbef7..4ae1bd15bf9f 100644 --- a/cli/unstable_spinner_test.ts +++ b/cli/unstable_spinner_test.ts @@ -166,6 +166,39 @@ Deno.test("Spinner constructor accepts interval", async () => { } }); +Deno.test("Spinner constructor accepts stream", async () => { + try { + stub(Deno.stdin, "setRaw"); + + const expectedOutput = [ + "\r\x1b[K⠋\x1b[0m ", + "\r\x1b[K⠙\x1b[0m ", + "\r\x1b[K⠹\x1b[0m ", + "\r\x1b[K", + ]; + + const actualOutput: string[] = []; + + stub( + Deno.stderr, + "writeSync", + (data: Uint8Array) => { + const output = decoder.decode(data); + actualOutput.push(output); + return data.length; + }, + ); + + const spinner = new Spinner({ interval: 300, stream: Deno.stderr }); + spinner.start(); + await delay(1000); // 100ms buffer + spinner.stop(); + assertEquals(actualOutput, expectedOutput); + } finally { + restore(); + } +}); + Deno.test("Spinner constructor accepts each color", async (t) => { await t.step("black", async () => { try {