Skip to content

Commit 94549a0

Browse files
committed
chore: initial release
0 parents  commit 94549a0

9 files changed

+205
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

assets/spinners-demo.gif

169 KB
Loading

bun.lockb

39 KB
Binary file not shown.

node_modules.bun

79.7 KB
Binary file not shown.

package.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "bun-spinners",
3+
"license": "MIT",
4+
"version": "0.0.3",
5+
"type": "module",
6+
"types": "./src/index.ts",
7+
"exports": {
8+
".": "./src/index.ts",
9+
"./*": "./src/*/index.ts"
10+
},
11+
"devDependencies": {
12+
"bun-types": "latest",
13+
"eslint": "^8.22.0"
14+
},
15+
"dependencies": {
16+
"bun-style": "^0.3.0",
17+
"cli-spinners": "^2.7.0"
18+
}
19+
}

readme.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Bun Spinners
2+
3+
Simple tool for logging any number of spinners.
4+
5+
#### Preview
6+
7+
![](/assets/spinners-demo.gif)
8+
9+
#### Preview code
10+
11+
See `test/index.test.ts`.
12+
13+
```ts
14+
import { spinners } from "bun-spinners";
15+
16+
const sleep = (ms: number) => new Promise(
17+
(resolve) => setTimeout(resolve, ms)
18+
);
19+
20+
try {
21+
await spinners({
22+
"Starting APU...": async () => {
23+
await sleep(1000);
24+
},
25+
26+
"Starting engines...": async () => {
27+
await sleep(5000);
28+
throw "Fuel mixture set to cutoff.";
29+
},
30+
31+
"Setting flaps...": async () => {
32+
await sleep(2000);
33+
}
34+
})
35+
} catch (e) {
36+
await spinners({
37+
"Aborting takeoff...": async () => {
38+
await sleep(3000);
39+
}
40+
});
41+
}
42+
```

src/index.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { LogStyles, style } from "bun-style";
2+
import * as cliSpinners from "cli-spinners";
3+
4+
export interface SpinnerConfigs {
5+
[text: string]: () => Promise<void>;
6+
}
7+
8+
export type SpinnerState = "running" | "success" | "failure";
9+
10+
export const spinners = async (
11+
configs: SpinnerConfigs,
12+
spinner: cliSpinners.SpinnerName = "dots",
13+
) => {
14+
let failed = 0;
15+
let finished = 0;
16+
17+
const spinnerEntries = Object.entries(configs);
18+
const spinnerStates: Map<string, SpinnerState> = new Map();
19+
const spinnerErrors = new Map<string, string>();
20+
21+
/**
22+
* Initialize all spinner states to "running".
23+
*/
24+
for (const [text] of spinnerEntries) {
25+
spinnerStates.set(
26+
text,
27+
"running"
28+
);
29+
}
30+
31+
await Promise.all([
32+
/**
33+
* Run spinners.
34+
*/
35+
(async () => {
36+
const { frames, interval } = cliSpinners[spinner];
37+
38+
while (finished < spinnerEntries.length) {
39+
for (const frame of frames) {
40+
let frameOutput = "";
41+
42+
for (const [text] of spinnerEntries) {
43+
const state = spinnerStates.get(text);
44+
45+
let prefix = frame;
46+
const lineStyles: LogStyles[] = ["bold"];
47+
48+
switch (state) {
49+
case "success":
50+
prefix = "✓";
51+
lineStyles.push("green")
52+
break;
53+
54+
case "failure":
55+
prefix = "✗";
56+
lineStyles.push("red")
57+
break;
58+
}
59+
60+
frameOutput += style(`\n ${prefix} ${text}\n\r`, lineStyles);
61+
62+
const errors = spinnerErrors.get(text);
63+
if (errors) {
64+
frameOutput += style(` ${errors}\n\r`, ["red"]);
65+
}
66+
}
67+
68+
console.log("\033[H\033[J");
69+
console.log(style(`${frameOutput}\n`, ["grey"]));
70+
await new Promise((resolve) => setTimeout(resolve, interval));
71+
}
72+
}
73+
})(),
74+
/**
75+
* Execute spinner processes.
76+
*/
77+
Promise.all(
78+
spinnerEntries.map(
79+
async ([text, fn]) => {
80+
spinnerStates.set(text, "running");
81+
try {
82+
await fn();
83+
spinnerStates.set(text, "success");
84+
} catch (e) {
85+
spinnerStates.set(text, "failure");
86+
spinnerErrors.set(text, e?.toString());
87+
failed++;
88+
} finally {
89+
finished++;
90+
}
91+
}
92+
)
93+
),
94+
]);
95+
96+
if (failed > 0) {
97+
throw `${failed} spinner(s) failed.`;
98+
}
99+
};

test/index.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { spinners } from "../src";
2+
3+
const sleep = (ms: number) => new Promise(
4+
(resolve) => setTimeout(resolve, ms)
5+
);
6+
7+
try {
8+
await spinners({
9+
"Starting APU...": async () => {
10+
await sleep(1000);
11+
},
12+
13+
"Starting engines...": async () => {
14+
await sleep(5000);
15+
throw "Fuel mixture set to cutoff.";
16+
},
17+
18+
"Setting flaps...": async () => {
19+
await sleep(2000);
20+
}
21+
})
22+
} catch (e) {
23+
await spinners({
24+
"Aborting takeoff...": async () => {
25+
await sleep(3000);
26+
}
27+
});
28+
}

tsconfig.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"strict": true,
3+
"compilerOptions": {
4+
"noEmit": true,
5+
"noImplicitAny": true,
6+
"moduleResolution": "node",
7+
"module": "esnext",
8+
"target": "esnext",
9+
"lib": [
10+
"ESNext"
11+
],
12+
"types": [
13+
"bun-types"
14+
]
15+
}
16+
}

0 commit comments

Comments
 (0)