• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

folke / ultra-runner / 4513047152

pending completion
4513047152

push

github

GitHub
chore(deps): update semantic-release monorepo

277 of 369 branches covered (75.07%)

Branch coverage included in aggregate %.

1517 of 2337 relevant lines covered (64.91%)

11.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.15
/src/spinner.ts
1
import chalk from "chalk"
1✔
2
// eslint-disable-next-line import/default
1✔
3
import { performance } from "perf_hooks"
1✔
4
import symbols from "./symbols"
1✔
5
import { Terminal } from "./terminal"
1✔
6

1✔
7
export enum SpinnerResult {
1✔
8
  success = 1,
1✔
9
  error,
1✔
10
  warning,
1✔
11
}
1✔
12

1✔
13
export class Spinner {
1✔
14
  result?: SpinnerResult
1✔
15
  start: number
1✔
16
  stop?: number
1✔
17
  output = ""
1✔
18

1✔
19
  constructor(public text: string, public level = 0) {
1✔
20
    this.start = performance.now()
15✔
21
  }
15✔
22

1✔
23
  format(symbol: string) {
1✔
24
    const padding = "".padEnd(this.level * 2)
292✔
25
    const output = this.output.length ? `\n${this.output}` : ""
292!
26
    if (this.result) {
292✔
27
      symbol = symbols.get(SpinnerResult[this.result])
203✔
28
      if (this.stop) {
203✔
29
        const duration = (this.stop - this.start) / 1000
203✔
30
        let du = `${duration.toFixed(3)}s`
203✔
31
        if (duration < 1) du = `${(duration * 1000).toFixed(0)}ms`
203✔
32
        return `${padding}${symbol} ${this.text} ${chalk.grey.dim(du)}${output}`
203✔
33
      }
203✔
34
    }
203✔
35
    return `${padding}${symbol} ${this.text}${output}`
89✔
36
  }
89✔
37
}
1✔
38

1✔
39
export class OutputSpinner {
1✔
40
  /* c8 ignore next */
1✔
41
  spinner =
1✔
42
    process.platform === "win32"
1✔
43
      ? {
1✔
44
          interval: 130,
1✔
45
          frames: ["-", "\\", "|", "/"],
1✔
46
        }
1✔
47
      : {
1!
48
          interval: 120,
×
49
          frames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
×
50
        }
×
51

1✔
52
  frame = 0
1✔
53
  interval: NodeJS.Timeout | undefined
1✔
54
  running = false
1✔
55
  spinnerMap = new Map<Spinner | undefined, Spinner[]>()
1✔
56
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1✔
57
  terminal: Terminal
1✔
58

1✔
59
  constructor(public stream = process.stdout) {
1✔
60
    this.terminal = new Terminal(stream)
6✔
61
  }
6✔
62

1✔
63
  render(full = false) {
1✔
64
    const symbol = chalk.yellow(this.spinner.frames[this.frame])
31✔
65

31✔
66
    let lineCount = 0
31✔
67
    const spinnerLines = this.spinners.map((spinner) => {
31✔
68
      const text = `${spinner.format(symbol)}`
292✔
69
      const lines = text.split("\n")
292✔
70
      lineCount += Math.min(lines.length, 3)
292✔
71
      return { count: Math.min(lines.length, 3), lines }
292✔
72
    })
31✔
73

31✔
74
    while (lineCount < process.stdout.rows) {
31!
75
      const loopLineCount = +lineCount
×
76
      spinnerLines.every((s) => {
×
77
        if (s.lines.length > s.count) {
×
78
          s.count++
×
79
          lineCount++
×
80
        }
×
81
        return lineCount < process.stdout.rows
×
82
      })
×
83
      if (lineCount == loopLineCount) break
×
84
    }
×
85

31✔
86
    const limitLines = (lines: string[], count: number) => {
31✔
87
      const ret = [lines[0]]
277✔
88
      if (count > 1) ret.push(...lines.slice(1 - count))
277!
89
      return ret
277✔
90
    }
277✔
91

31✔
92
    let text = `${spinnerLines
31✔
93
      .map((s) => (full ? s.lines : limitLines(s.lines, s.count)).join("\n"))
31✔
94
      .join("\n")}\n`
31✔
95

31✔
96
    if (!full) text = text.trim()
31✔
97
    let lines = text.split("\n")
31✔
98
    if (!full) lines = lines.slice(-process.stdout.rows)
31✔
99

31✔
100
    this.terminal.update(lines)
31✔
101
  }
31✔
102

1✔
103
  get spinners(): Spinner[] {
1✔
104
    const ret = new Array<Spinner>()
31✔
105
    // eslint-disable-next-line unicorn/no-useless-undefined
31✔
106
    const queue = this.spinnerMap.get(undefined)?.slice() ?? []
31!
107
    while (queue.length) {
31✔
108
      const spinner = queue.shift() as Spinner
292✔
109
      ret.push(spinner)
292✔
110
      queue.unshift(...(this.spinnerMap.get(spinner) || []))
292✔
111
    }
292✔
112
    return ret
31✔
113
  }
31✔
114

1✔
115
  start(text: string, level = 0, parentSpinner?: Spinner) {
1✔
116
    const s = new Spinner(text, level)
15✔
117
    if (!this.spinnerMap.has(parentSpinner))
15✔
118
      this.spinnerMap.set(parentSpinner, [])
15✔
119
    this.spinnerMap.get(parentSpinner)?.push(s)
15!
120

15✔
121
    // this.spinners.push(s)
15✔
122
    if (!this.running) this._start()
15✔
123
    this.render()
15✔
124
    return s
15✔
125
  }
15✔
126

1✔
127
  stop(spinner: Spinner) {
1✔
128
    spinner.stop = performance.now()
15✔
129
    this.render()
15✔
130
  }
15✔
131

1✔
132
  error(spinner: Spinner) {
1✔
133
    spinner.result = SpinnerResult.error
5✔
134
    this.stop(spinner)
5✔
135
  }
5✔
136

1✔
137
  warning(spinner: Spinner) {
1✔
138
    spinner.result = SpinnerResult.warning
1✔
139
    this.stop(spinner)
1✔
140
  }
1✔
141

1✔
142
  success(spinner: Spinner) {
1✔
143
    spinner.result = SpinnerResult.success
15✔
144
    this.stop(spinner)
15✔
145
  }
15✔
146

1✔
147
  _start() {
1✔
148
    /* c8 ignore next */
1✔
149
    if (this.running) return
1✔
150
    this.running = true
1✔
151
    this.interval = setInterval(() => {
1✔
152
      /* c8 ignore next 2 */
1✔
153
      this.frame = ++this.frame % this.spinner.frames.length
1✔
154
      this.render()
1✔
155
    }, this.spinner.interval)
1✔
156
  }
1✔
157

1✔
158
  _stop() {
1✔
159
    if (this.running) {
10✔
160
      if (this.interval) clearInterval(this.interval)
1✔
161
      this.render(true)
1✔
162
      this.interval = undefined
1✔
163
      this.running = false
1✔
164
      this.spinnerMap.clear()
1✔
165
    }
1✔
166
  }
10✔
167
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc