Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

ora

Package Overview
Dependencies
Maintainers
1
Versions
55
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ora - npm Package Compare versions

Comparing version
8.2.0
to
9.0.0
+1
-1
index.d.ts

@@ -40,3 +40,3 @@ import {type SpinnerName} from 'cli-spinners';

/**
The name of one of the provided spinners. See [`example.js`](https://github.com/BendingBender/ora/blob/main/example.js) in this repo if you want to test out different spinners. On Windows (expect for Windows Terminal), it will always use the line spinner as the Windows command-line doesn't have proper Unicode support.
The name of one of the provided spinners. See `example.js` in this repo if you want to test out different spinners. On Windows (except for Windows Terminal), it will always use the line spinner as the Windows command-line doesn't have proper Unicode support.

@@ -43,0 +43,0 @@ @default 'dots'

+67
-48

@@ -18,2 +18,3 @@ import process from 'node:process';

#lastSpinnerFrameTime = 0;
#lastIndent = 0;
#options;

@@ -118,6 +119,10 @@ #spinner;

if (typeof spinner === 'object') {
if (spinner.frames === undefined) {
throw new Error('The given spinner must have a `frames` property');
if (!Array.isArray(spinner.frames) || spinner.frames.length === 0 || spinner.frames.some(frame => typeof frame !== 'string')) {
throw new Error('The given spinner must have a non-empty `frames` array of strings');
}
if (spinner.interval !== undefined && !(Number.isInteger(spinner.interval) && spinner.interval > 0)) {
throw new Error('`spinner.interval` must be a positive integer if provided');
}
this.#spinner = spinner;

@@ -167,24 +172,26 @@ } else if (!isUnicodeSupported()) {

#getFullPrefixText(prefixText = this.#prefixText, postfix = ' ') {
if (typeof prefixText === 'string' && prefixText !== '') {
return prefixText + postfix;
#formatAffix(value, separator, placeBefore = false) {
const resolved = typeof value === 'function' ? value() : value;
if (typeof resolved === 'string' && resolved !== '') {
return placeBefore ? (separator + resolved) : (resolved + separator);
}
if (typeof prefixText === 'function') {
return prefixText() + postfix;
}
return '';
}
#getFullPrefixText(prefixText = this.#prefixText, postfix = ' ') {
return this.#formatAffix(prefixText, postfix, false);
}
#getFullSuffixText(suffixText = this.#suffixText, prefix = ' ') {
if (typeof suffixText === 'string' && suffixText !== '') {
return prefix + suffixText;
}
return this.#formatAffix(suffixText, prefix, true);
}
if (typeof suffixText === 'function') {
return prefix + suffixText();
#computeLineCountFrom(text, columns) {
let count = 0;
for (const line of stripAnsi(text).split('\n')) {
count += Math.max(1, Math.ceil(stringWidth(line) / columns));
}
return '';
return count;
}

@@ -194,10 +201,12 @@

const columns = this.#stream.columns ?? 80;
const fullPrefixText = this.#getFullPrefixText(this.#prefixText, '-');
const fullSuffixText = this.#getFullSuffixText(this.#suffixText, '-');
const fullText = ' '.repeat(this.#indent) + fullPrefixText + '--' + this.#text + '--' + fullSuffixText;
this.#lineCount = 0;
for (const line of stripAnsi(fullText).split('\n')) {
this.#lineCount += Math.max(1, Math.ceil(stringWidth(line, {countAnsiEscapeCodes: true}) / columns));
}
// Simple side-effect free approximation (do not call functions)
const prefixText = typeof this.#prefixText === 'function' ? '' : this.#prefixText;
const suffixText = typeof this.#suffixText === 'function' ? '' : this.#suffixText;
const fullPrefixText = (typeof prefixText === 'string' && prefixText !== '') ? prefixText + ' ' : '';
const fullSuffixText = (typeof suffixText === 'string' && suffixText !== '') ? ' ' + suffixText : '';
const spinnerChar = '-';
const fullText = ' '.repeat(this.#indent) + fullPrefixText + spinnerChar + (typeof this.#text === 'string' ? ' ' + this.#text : '') + fullSuffixText;
this.#lineCount = this.#computeLineCountFrom(fullText, columns);
}

@@ -245,5 +254,5 @@

const fullPrefixText = (typeof this.#prefixText === 'string' && this.#prefixText !== '') ? this.#prefixText + ' ' : '';
const fullPrefixText = this.#getFullPrefixText(this.#prefixText, ' ');
const fullText = typeof this.text === 'string' ? ' ' + this.text : '';
const fullSuffixText = (typeof this.#suffixText === 'string' && this.#suffixText !== '') ? ' ' + this.#suffixText : '';
const fullSuffixText = this.#getFullSuffixText(this.#suffixText, ' ');

@@ -268,7 +277,7 @@ return fullPrefixText + frame + fullText + fullSuffixText;

if (this.#indent || this.lastIndent !== this.#indent) {
if (this.#indent || this.#lastIndent !== this.#indent) {
this.#stream.cursorTo(this.#indent);
}
this.lastIndent = this.#indent;
this.#lastIndent = this.#indent;
this.#linesToClear = 0;

@@ -280,3 +289,3 @@

render() {
if (this.#isSilent) {
if (!this.#isEnabled || this.#isSilent) {
return this;

@@ -286,5 +295,18 @@ }

this.clear();
this.#stream.write(this.frame());
this.#linesToClear = this.#lineCount;
let frameContent = this.frame();
const columns = this.#stream.columns ?? 80;
const actualLineCount = this.#computeLineCountFrom(frameContent, columns);
// If content would exceed viewport height, truncate it to prevent garbage
const consoleHeight = this.#stream.rows;
if (consoleHeight && consoleHeight > 1 && actualLineCount > consoleHeight) {
const lines = frameContent.split('\n');
const maxLines = consoleHeight - 1; // Reserve one line for truncation message
frameContent = [...lines.slice(0, maxLines), '... (content truncated to fit terminal)'].join('\n');
}
this.#stream.write(frameContent);
this.#linesToClear = this.#computeLineCountFrom(frameContent, columns);
return this;

@@ -303,4 +325,6 @@ }

if (!this.#isEnabled) {
if (this.text) {
this.#stream.write(`- ${this.text}\n`);
const line = ' '.repeat(this.#indent) + this.#getFullPrefixText(this.#prefixText, ' ') + (this.text ? `- ${this.text}` : '') + this.#getFullSuffixText(this.#suffixText, ' ');
if (line.trim() !== '') {
this.#stream.write(line + '\n');
}

@@ -331,12 +355,11 @@

stop() {
if (!this.#isEnabled) {
return this;
}
clearInterval(this.#id);
this.#id = undefined;
this.#frameIndex = 0;
this.clear();
if (this.#options.hideCursor) {
cliCursor.show(this.#stream);
if (this.#isEnabled) {
this.clear();
if (this.#options.hideCursor) {
cliCursor.show(this.#stream);
}
}

@@ -416,15 +439,11 @@

spinner.succeed(
successText === undefined
? undefined
: (typeof successText === 'string' ? successText : successText(result)),
);
spinner.succeed(successText === undefined
? undefined
: (typeof successText === 'string' ? successText : successText(result)));
return result;
} catch (error) {
spinner.fail(
failText === undefined
? undefined
: (typeof failText === 'string' ? failText : failText(error)),
);
spinner.fail(failText === undefined
? undefined
: (typeof failText === 'string' ? failText : failText(error)));

@@ -431,0 +450,0 @@ throw error;

{
"name": "ora",
"version": "8.2.0",
"version": "9.0.0",
"description": "Elegant terminal spinner",

@@ -20,6 +20,6 @@ "license": "MIT",

"engines": {
"node": ">=18"
"node": ">=20"
},
"scripts": {
"test": "xo && ava && tsd"
"test": "xo && NODE_ENV=test node --test test.js && tsd"
},

@@ -47,20 +47,20 @@ "files": [

"dependencies": {
"chalk": "^5.3.0",
"chalk": "^5.6.2",
"cli-cursor": "^5.0.0",
"cli-spinners": "^2.9.2",
"cli-spinners": "^3.2.0",
"is-interactive": "^2.0.0",
"is-unicode-supported": "^2.0.0",
"log-symbols": "^6.0.0",
"is-unicode-supported": "^2.1.0",
"log-symbols": "^7.0.1",
"stdin-discarder": "^0.2.2",
"string-width": "^7.2.0",
"strip-ansi": "^7.1.0"
"string-width": "^8.1.0",
"strip-ansi": "^7.1.2"
},
"devDependencies": {
"@types/node": "^22.5.0",
"ava": "^5.3.1",
"@types/node": "^24.5.0",
"ava": "^6.4.1",
"get-stream": "^9.0.1",
"transform-tty": "^1.0.11",
"tsd": "^0.31.1",
"xo": "^0.59.3"
"tsd": "^0.33.0",
"xo": "^1.2.2"
}
}

@@ -83,3 +83,3 @@ # ora

The color of the spinner.
The color of the spinner. Set to `false` to disable coloring.

@@ -316,2 +316,48 @@ ##### hideCursor

### Can I display multiple spinners simultaneously?
No. Ora is designed to display a single spinner at a time. For multiple concurrent progress indicators, consider alternatives like [listr2](https://github.com/listr2/listr2) or [spinnies](https://github.com/jcarpanelli/spinnies).
### Can I use Ora with [log-update](https://github.com/sindresorhus/log-update)?
Yes, use the `.frame()` method to get the current spinner frame and include it in your log-update output.
### Does Ora work in Node.js Worker threads?
No. Ora requires an interactive terminal environment and Worker threads are not considered interactive, so the spinner will not animate. Run the spinner in the main thread and control it via worker messages:
```js
// main.js
import {Worker} from 'node:worker_threads';
import ora from 'ora';
const spinner = ora().start();
const worker = new Worker('./worker.js');
worker.on('message', message => {
switch (message.type) {
case 'ora:text':
spinner.text = message.text;
break;
case 'ora:succeed':
spinner.succeed(message.text);
break;
case 'ora:fail':
spinner.fail(message.text);
break;
}
});
```
```js
// worker.js
import {parentPort} from 'node:worker_threads';
parentPort.postMessage({type: 'ora:text', text: 'Working...'});
// Do work...
parentPort.postMessage({type: 'ora:succeed', text: 'Done!'});
```
## Related

@@ -318,0 +364,0 @@