Skip to content

Commit 9c2d74f

Browse files
committed
feat(cli): implement sweet spinner and progress
1 parent f83180b commit 9c2d74f

File tree

5 files changed

+148
-70
lines changed

5 files changed

+148
-70
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@
135135
"@typescript-eslint/eslint-plugin": "^4.14.1",
136136
"@typescript-eslint/parser": "^4.14.1",
137137
"@typescript-eslint/typescript-estree": "^4.14.1",
138+
"ansi-escapes": "^4.0.0",
138139
"babel-eslint": "^10.1.0",
139140
"babel-plugin-istanbul": "^6.0.0",
140141
"chai": "^4.2.0",

pnpm-lock.yaml

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cli/spinner.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import chalk from 'chalk'
2+
3+
const frames = [
4+
chalk`{bold } ASTX `,
5+
chalk`{bold ▰} ASTX `,
6+
chalk`{bold ▰▰} ASTX `,
7+
chalk`{bold ▰▰▰} ASTX `,
8+
chalk` {bold ▰▰▰} ASTX `,
9+
chalk` {bold ▰▰▰}ASTX `,
10+
chalk` {bold ▰▰A}STX `,
11+
chalk` {bold ▰AS}TX `,
12+
chalk` {bold AST}X `,
13+
chalk` A{bold STX} `,
14+
chalk` AS{bold TX▰} `,
15+
chalk` AST{bold X▰▰} `,
16+
chalk` ASTX{bold ▰▰▰} `,
17+
chalk` ASTX {bold ▰▰▰} `,
18+
chalk` ASTX {bold ▰▰▰}`,
19+
chalk` ASTX {bold ▰▰}`,
20+
chalk` ASTX {bold ▰}`,
21+
]
22+
23+
let start: number | undefined
24+
25+
export function spinner(): string {
26+
if (start == null) start = Date.now()
27+
return frames[Math.floor((Date.now() - start) / 120) % frames.length]
28+
}

src/cli/transform.ts

+115-68
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import { formatIpcMatches } from '../util/formatMatches'
1212
import { AstxWorkerPool, astxCosmiconfig } from '../node'
1313
import { invertIpcError } from '../node/ipc'
1414
import { Transform } from '../Astx'
15+
import ansiEscapes from 'ansi-escapes'
16+
import { Progress } from '../node/AstxWorkerPool'
17+
import { spinner } from './spinner'
1518

1619
/* eslint-disable no-console */
1720

@@ -64,6 +67,8 @@ const transform: CommandModule<Options> = {
6467
}),
6568

6669
handler: async (argv: Arguments<Options>) => {
70+
const startTime = Date.now()
71+
6772
const paths = (argv.filesAndDirectories || []).filter(
6873
(x) => typeof x === 'string'
6974
) as string[]
@@ -93,91 +98,133 @@ const transform: CommandModule<Options> = {
9398
let errorCount = 0
9499
let changedCount = 0
95100
let unchangedCount = 0
101+
102+
let progress: Progress = {
103+
type: 'progress',
104+
completed: 0,
105+
total: 0,
106+
globDone: false,
107+
}
108+
109+
let progressDisplayed = false
110+
function clearProgress() {
111+
if (progressDisplayed) {
112+
process.stderr.write(ansiEscapes.cursorLeft + ansiEscapes.eraseLine)
113+
progressDisplayed = false
114+
}
115+
}
116+
function showProgress() {
117+
clearProgress()
118+
progressDisplayed = true
119+
const { completed, total, globDone } = progress
120+
process.stderr.write(
121+
chalk.magenta(
122+
`${spinner()} Running... ${completed}/${total}${
123+
globDone && total
124+
? ` (${((completed * 100) / total).toFixed(1)}%)`
125+
: ''
126+
} ${((Date.now() - startTime) / 1000).toFixed(2)}s`
127+
)
128+
)
129+
}
130+
let spinnerInterval
131+
96132
const config = (await astxCosmiconfig.search())?.config
97133
const pool = new AstxWorkerPool({ capacity: config?.workers })
98-
for await (const event of pool.runTransform({
99-
transform,
100-
transformFile,
101-
paths,
102-
config: {
103-
parser: parser as any,
104-
parserOptions: parserOptions ? JSON.parse(parserOptions) : undefined,
105-
},
106-
})) {
107-
if (event.type === 'progress') {
108-
continue
134+
try {
135+
if (process.stderr.isTTY) {
136+
spinnerInterval = setInterval(showProgress, 30)
109137
}
110-
const {
111-
file,
112-
source,
113-
transformed,
114-
reports,
115-
matches,
116-
error: _error,
117-
} = event.result
118-
const error = _error ? invertIpcError(_error) : undefined
119-
const relpath = path.relative(process.cwd(), file)
120-
const logHeader = once((logFn: (value: string) => any) =>
121-
logFn(
122-
chalk.blue(dedent`
138+
for await (const event of pool.runTransform({
139+
transform,
140+
transformFile,
141+
paths,
142+
config: {
143+
parser: parser as any,
144+
parserOptions: parserOptions ? JSON.parse(parserOptions) : undefined,
145+
},
146+
})) {
147+
if (event.type === 'progress') {
148+
progress = event
149+
if (process.stderr.isTTY) showProgress()
150+
continue
151+
}
152+
clearProgress()
153+
const {
154+
file,
155+
source,
156+
transformed,
157+
reports,
158+
matches,
159+
error: _error,
160+
} = event.result
161+
const error = _error ? invertIpcError(_error) : undefined
162+
const relpath = path.relative(process.cwd(), file)
163+
const logHeader = once((logFn: (value: string) => any) =>
164+
logFn(
165+
chalk.blue(dedent`
123166
${'='.repeat(relpath.length)}
124167
${chalk.bold(relpath)}
125168
${'='.repeat(relpath.length)}
126169
`)
170+
)
127171
)
128-
)
129172

130-
if (error) {
131-
errorCount++
132-
logHeader(console.error)
133-
if (error instanceof CodeFrameError) {
134-
console.error(
135-
error.format({
136-
highlightCode: true,
137-
forceColor: true,
138-
stack: true,
139-
})
140-
)
141-
} else {
142-
console.error(chalk.red(error.stack))
143-
}
144-
} else if (source && transformed && source !== transformed) {
145-
changedCount++
146-
results[file] = transformed
147-
if (!argv.yes) {
173+
if (error) {
174+
errorCount++
175+
logHeader(console.error)
176+
if (error instanceof CodeFrameError) {
177+
console.error(
178+
error.format({
179+
highlightCode: true,
180+
forceColor: true,
181+
stack: true,
182+
})
183+
)
184+
} else {
185+
console.error(chalk.red(error.stack))
186+
}
187+
} else if (source && transformed && source !== transformed) {
188+
changedCount++
189+
results[file] = transformed
190+
if (!argv.yes) {
191+
logHeader(console.log)
192+
console.log(formatDiff(source, transformed))
193+
}
194+
} else if (
195+
matches?.length &&
196+
source &&
197+
transform.find &&
198+
!transform.replace &&
199+
!transform.astx
200+
) {
148201
logHeader(console.log)
149-
console.log(formatDiff(source, transformed))
202+
console.log(formatIpcMatches(source, matches))
203+
} else {
204+
unchangedCount++
150205
}
151-
} else if (
152-
matches?.length &&
153-
source &&
154-
transform.find &&
155-
!transform.replace &&
156-
!transform.astx
157-
) {
158-
logHeader(console.log)
159-
console.log(formatIpcMatches(source, matches))
160-
} else {
161-
unchangedCount++
162-
}
163206

164-
if (reports?.length) {
165-
logHeader(console.error)
166-
console.error(
167-
chalk.blue(dedent`
207+
if (reports?.length) {
208+
logHeader(console.error)
209+
console.error(
210+
chalk.blue(dedent`
168211
Reports
169212
-------
170213
`)
171-
)
172-
reports?.forEach((r: any) =>
173-
console.error(
174-
// r instanceof Astx && source
175-
// ? formatIpcMatches(source, r.matches)
176-
// : r
177-
r
178214
)
179-
)
215+
reports?.forEach((r: any) =>
216+
console.error(
217+
// r instanceof Astx && source
218+
// ? formatIpcMatches(source, r.matches)
219+
// : r
220+
r
221+
)
222+
)
223+
}
180224
}
225+
} finally {
226+
if (spinnerInterval != null) clearInterval(spinnerInterval)
227+
clearProgress()
181228
}
182229

183230
if (transform.replace || transform.astx) {

src/node/AstxWorkerPool.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export default class AstxWorkerPool {
6666
if (signal?.aborted) return
6767
total++
6868
yield progress()
69+
if (signal?.aborted) return
6970
const promise = this.runTransformOnFile({
7071
file,
7172
transform,
@@ -79,6 +80,7 @@ export default class AstxWorkerPool {
7980
promises.push(promise)
8081
}
8182
}
83+
if (signal?.aborted) return
8284
globDone = true
8385
yield progress()
8486

@@ -89,7 +91,5 @@ export default class AstxWorkerPool {
8991
completed++
9092
yield progress()
9193
}
92-
if (signal?.aborted) return
93-
yield progress()
9494
}
9595
}

0 commit comments

Comments
 (0)