Resin CLI Visuals
resin-cli-visuals is a JavaScript/TypeScript library providing a collection of command-line interface (CLI) widgets designed primarily for the Balena ecosystem's CLI tools. It offers components such as spinners, progress bars, interactive tables (horizontal and vertical), and a drive scanner to dynamically detect storage device changes. The current stable version is 4.0.4, released in March 2026, indicating an active development cadence with several minor and major releases in the last year. Key differentiators include its focus on CLI UX within the Balena context, offering ready-to-use visual components for common CLI interactions, and its recent migration to TypeScript and ESM, ensuring modern JavaScript practices. The library is built to enhance user interaction in console applications, making operations like long-running tasks or data display more visually intuitive.
Common errors
-
ERR_REQUIRE_ESM
cause Attempting to use `require()` to import an ES Module package (resin-cli-visuals >= v4.0.0).fixChange `const visuals = require('resin-cli-visuals');` to `import { visuals } from 'resin-cli-visuals';`. Ensure your `package.json` has `"type": "module"` or use `.mjs` file extensions. -
TypeError: visuals.SpinnerPromise is not a constructor
cause Using the `SpinnerPromise` class constructor directly after v4.0.0.fixReplace `new visuals.SpinnerPromise(options)` with `visuals.createSpinnerPromise(options)` as it was changed to a factory function. -
Error: Cannot find module 'resin-cli-visuals'
cause Incorrect Node.js version. The package specifies `engines.node` requirements.fixCheck your Node.js version (`node -v`) and ensure it meets the `>=20.0` requirement for v4.x of `resin-cli-visuals`. Upgrade Node.js if needed.
Warnings
- breaking The package migrated entirely to ES Modules (ESM) and TypeScript in v4.0.0. CommonJS `require()` statements will no longer work, and consumers must use `import` syntax.
- breaking Node.js version requirements increased significantly. v4.x requires Node.js >=20.0, v3.x required >=18.0, and v2.x also required >=18.0.
- breaking The `SpinnerPromise` class was replaced with a `createSpinnerPromise` factory function in v4.0.0. The direct `new SpinnerPromise()` constructor is no longer available.
- breaking The `chalk` dependency was dropped and replaced with `ansis` in v3.0.1. While `ansis` aims for `chalk` compatibility, direct imports or specific configurations related to `chalk` might break.
- breaking The internal `lodash` dependency was removed in v4.0.4. While this should primarily affect internal implementation, if any consumers relied on transitive `lodash` access via `resin-cli-visuals` (an anti-pattern), it would now be missing.
Install
-
npm install resin-cli-visuals -
yarn add resin-cli-visuals -
pnpm add resin-cli-visuals
Imports
- visuals
const visuals = require('resin-cli-visuals');import { visuals } from 'resin-cli-visuals'; - DriveScanner
import DriveScanner from 'resin-cli-visuals/build/DriveScanner';
import { DriveScanner } from 'resin-cli-visuals'; - Spinner
import { Spinner } from 'resin-cli-visuals';import { visuals } from 'resin-cli-visuals'; const spinner = new visuals.Spinner('Loading...');
Quickstart
import { visuals, DriveScanner } from 'resin-cli-visuals';
async function runCliDemo() {
// Simulate a long-running operation with a spinner
const spinner = new visuals.Spinner('Processing task...');
spinner.start();
await new Promise(resolve => setTimeout(resolve, 3000));
spinner.stop();
console.log('Task completed.');
// Display data in a horizontal table
const tableData = [
{ label: 'Name', value: 'John Doe' },
{ label: 'Email', value: 'john.doe@example.com' },
{ label: 'Role', value: 'Developer' }
];
const tableOrdering = ['label', 'value'];
console.log('\nUser Details:');
console.log(visuals.table.horizontal(tableData, tableOrdering));
// Example of a DriveScanner (requires a driveFinder function)
// In a real scenario, driveFinder would be an async function
// that returns an array of drive objects.
const mockDriveFinder = async () => [
{ device: '/dev/sda', size: '10GB', description: 'USB Drive' },
{ device: '/dev/sdb', size: '500GB', description: 'Internal HDD' }
];
const scanner = new DriveScanner(mockDriveFinder, { interval: 2000 });
scanner.on('change', (drives) => {
console.log('\nDrive change detected:', drives);
});
console.log('\nDrive scanner started. Waiting for changes (runs for 5s)...');
await new Promise(resolve => setTimeout(resolve, 5000));
scanner.stop();
console.log('Drive scanner stopped.');
}
runCliDemo().catch(console.error);