Skip to content

Commit f9527eb

Browse files
authored
refactor!: use Node.js core and remove most dependencies (#196)
1 parent ee3e693 commit f9527eb

8 files changed

Lines changed: 137 additions & 37 deletions

File tree

.github/workflows/node.js.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ on:
55
branches: [main]
66
pull_request:
77

8+
env:
9+
FORCE_COLOR: "1"
10+
811
permissions:
912
contents: read
1013

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ All available spinners are part of [cli-spinners](https://github.com/sindresorhu
1616

1717
## Requirements
1818

19-
- [Node.js](https://nodejs.org/en/) v20 or higher
19+
- [Node.js](https://nodejs.org/en/) v22 or higher
2020

2121
## Getting Started
2222

package.json

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,15 @@
33
"version": "2.1.2",
44
"description": "Asynchronous CLI Spinner. Allow to create and manage simultaneous/multiple spinners at a time.",
55
"main": "./dist/index.js",
6-
"module": "./dist/index.mjs",
76
"types": "./dist/index.d.ts",
8-
"exports": {
9-
".": {
10-
"types": "./dist/index.d.ts",
11-
"require": "./dist/index.cjs",
12-
"import": "./dist/index.js"
13-
}
14-
},
7+
"type": "module",
158
"scripts": {
16-
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
9+
"build": "tsc",
1710
"prepublishOnly": "npm run build",
18-
"test-only": "tsx --test test/Spinner.spec.ts",
11+
"test-only": "tsx --test test/**.spec.ts",
1912
"test": "npm run lint && npm run coverage",
2013
"coverage": "c8 -r html npm run test-only",
21-
"lint": "eslint .",
22-
"lint:fix": "eslint . --fix"
14+
"lint": "eslint src test"
2315
},
2416
"repository": {
2517
"type": "git",
@@ -46,24 +38,17 @@
4638
},
4739
"homepage": "https://github.com/TopCli/Spinner#readme",
4840
"dependencies": {
49-
"@topcli/wcwidth": "^1.0.1",
50-
"ansi-regex": "^6.0.1",
51-
"cli-cursor": "^5.0.0",
52-
"cli-spinners": "^3.1.0",
53-
"kleur": "^4.1.5",
54-
"strip-ansi": "^7.1.0"
41+
"cli-spinners": "^3.1.0"
5542
},
5643
"devDependencies": {
5744
"@openally/config.eslint": "^2.0.0",
5845
"@openally/config.typescript": "^1.0.3",
5946
"@types/node": "^22.10.2",
6047
"c8": "^10.1.2",
61-
"tsup": "^8.1.0",
6248
"tsx": "^4.16.2",
6349
"typescript": "^5.5.3"
6450
},
6551
"engines": {
66-
"node": ">=20"
67-
},
68-
"type": "module"
52+
"node": ">=22"
53+
}
6954
}

src/Spinner.class.ts

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
11
// Import Node.js Dependencies
22
import { EventEmitter } from "node:events";
33
import { performance } from "node:perf_hooks";
4+
import { inspect, styleText } from "node:util";
45
import readline from "node:readline";
56
import * as TTY from "node:tty";
67

78
// Import Third-party Dependencies
89
import * as cliSpinners from "cli-spinners";
9-
import stripAnsi from "strip-ansi";
10-
import ansiRegex from "ansi-regex";
11-
import wcwidth from "@topcli/wcwidth";
12-
import kleur from "kleur";
10+
11+
// Import Internal Dependencies
12+
import {
13+
stringLength,
14+
type Color
15+
} from "./utils/index.js";
1316

1417
// VARS
1518
let internalSpinnerCount = 0;
1619

1720
// CONSTANTS
1821
const kDefaultSpinnerName = "dots" satisfies cliSpinners.SpinnerName;
22+
const kAvailableColors = new Set<Color>(
23+
Object.keys(inspect.colors) as Color[]
24+
);
25+
1926
const kLogSymbols = process.platform !== "win32" || process.env.CI || process.env.TERM === "xterm-256color" ?
20-
{ success: kleur.bold().green("✔"), error: kleur.bold().red("✖") } :
21-
{ success: kleur.bold().green("√"), error: kleur.bold().red("×") };
27+
{ success: styleText("green", "✔"), error: styleText("red", "✖") } :
28+
{ success: styleText("green", "√"), error: styleText("red", "×") };
2229

2330
export interface ISpinnerOptions {
2431
/**
@@ -32,7 +39,7 @@ export interface ISpinnerOptions {
3239
*
3340
* @default "white"
3441
*/
35-
color?: string;
42+
color?: Color;
3643
/**
3744
* Do not log anything when disabled
3845
*
@@ -73,12 +80,18 @@ export class Spinner extends EventEmitter {
7380

7481
const { name = kDefaultSpinnerName, color = null } = options;
7582

76-
this.#spinner = name in cliSpinners ? cliSpinners[name] : cliSpinners[kDefaultSpinnerName];
83+
this.#spinner = name in cliSpinners ?
84+
cliSpinners.default[name] :
85+
cliSpinners.default[kDefaultSpinnerName];
7786
if (color === null) {
7887
this.#color = (str: string) => str;
7988
}
8089
else {
81-
this.#color = color in kleur ? kleur[color] : kleur.white;
90+
const colorArr = Array.isArray(color) ? color : [color];
91+
92+
this.#color = colorArr.every((color) => kAvailableColors.has(color)) ?
93+
(str) => styleText(color, str) :
94+
(str) => styleText("white", str);
8295
}
8396
}
8497

@@ -135,9 +148,9 @@ export class Spinner extends EventEmitter {
135148
}
136149
count = regexArray.length;
137150
}
138-
count += regexArray!.reduce((prev, curr) => prev + wcwidth(curr), 0);
151+
count += regexArray!.reduce((prev, curr) => prev + stringLength(curr), 0);
139152

140-
return wcwidth(stripAnsi(defaultRaw)) > terminalCol ?
153+
return stringLength(defaultRaw) > terminalCol ?
141154
`${defaultRaw.slice(0, terminalCol + count)}\x1B[0m` :
142155
defaultRaw;
143156
}
@@ -153,7 +166,7 @@ export class Spinner extends EventEmitter {
153166
const line = this.#lineToRender(spinnerSymbol);
154167
readline.clearLine(this.stream, 0);
155168
this.stream.write(line);
156-
readline.moveCursor(this.stream, -(wcwidth(line)), moveCursorPos);
169+
readline.moveCursor(this.stream, -(stringLength(line)), moveCursorPos);
157170
}
158171

159172
start(text?: string, options: IStartOptions = {}) {
@@ -210,3 +223,17 @@ export class Spinner extends EventEmitter {
210223
return this;
211224
}
212225
}
226+
227+
/**
228+
* @note code copy-pasted from https://github.com/chalk/ansi-regex#readme
229+
*/
230+
function ansiRegex() {
231+
// Valid string terminator sequences are BEL, ESC\, and 0x9c
232+
const ST = "(?:\\u0007|\\u001B\\u005C|\\u009C)";
233+
const pattern = [
234+
`[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?${ST})`,
235+
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))"
236+
].join("|");
237+
238+
return new RegExp(pattern, "g");
239+
}

src/utils/colors.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
type ForegroundColors =
2+
| "black"
3+
| "blackBright"
4+
| "blue"
5+
| "blueBright"
6+
| "cyan"
7+
| "cyanBright"
8+
| "gray"
9+
| "green"
10+
| "greenBright"
11+
| "grey"
12+
| "magenta"
13+
| "magentaBright"
14+
| "red"
15+
| "redBright"
16+
| "white"
17+
| "whiteBright"
18+
| "yellow"
19+
| "yellowBright";
20+
21+
type BackgroundColors =
22+
| "bgBlack"
23+
| "bgBlackBright"
24+
| "bgBlue"
25+
| "bgBlueBright"
26+
| "bgCyan"
27+
| "bgCyanBright"
28+
| "bgGray"
29+
| "bgGreen"
30+
| "bgGreenBright"
31+
| "bgGrey"
32+
| "bgMagenta"
33+
| "bgMagentaBright"
34+
| "bgRed"
35+
| "bgRedBright"
36+
| "bgWhite"
37+
| "bgWhiteBright"
38+
| "bgYellow"
39+
| "bgYellowBright";
40+
41+
type Modifiers =
42+
| "blink"
43+
| "bold"
44+
| "dim"
45+
| "doubleunderline"
46+
| "framed"
47+
| "hidden"
48+
| "inverse"
49+
| "italic"
50+
| "overlined"
51+
| "reset"
52+
| "strikethrough"
53+
| "underline";
54+
55+
export type Color =
56+
| ForegroundColors
57+
| BackgroundColors
58+
| Modifiers
59+
| Array<ForegroundColors | BackgroundColors | Modifiers>;

src/utils/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./stringLength.js";
2+
export * from "./colors.js";

src/utils/stringLength.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Import Node.js Dependencies
2+
import {
3+
stripVTControlCharacters
4+
} from "node:util";
5+
6+
// CONSTANTS
7+
const kLenSegmenter = new Intl.Segmenter();
8+
9+
export function stringLength(
10+
string: string
11+
): number {
12+
if (string === "") {
13+
return 0;
14+
}
15+
16+
let length = 0;
17+
for (const _ of kLenSegmenter.segment(
18+
stripVTControlCharacters(string)
19+
)) {
20+
length++;
21+
}
22+
23+
return length;
24+
}

tsconfig.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
"extends": "@openally/config.typescript",
33
"compilerOptions": {
44
"outDir": "dist",
5-
"rootDir": "./",
5+
"rootDir": "./src",
66
},
7-
"include": ["src", "index.ts"],
7+
"include": ["src"],
88
"exclude": ["node_modules", "dist"]
99
}

0 commit comments

Comments
 (0)