()
| 23 | } |
| 24 | |
| 25 | export function createShimmerProgress(): ShimmerProgress { |
| 26 | // Piped/redirected stdout: `\r`-rewriting animation frames are garbage in a |
| 27 | // log file — emit one plain line per phase instead (#1281). |
| 28 | if (process.stdout.isTTY !== true) { |
| 29 | return createPlainProgress(); |
| 30 | } |
| 31 | |
| 32 | const useColor = ansiColorsEnabled(); |
| 33 | const G = getGlyphs(); |
| 34 | const DM = useColor ? '\x1b[2m' : ''; |
| 35 | const GRN = useColor ? '\x1b[32m' : ''; |
| 36 | const RST = useColor ? '\x1b[0m' : ''; |
| 37 | |
| 38 | let lastPhase = ''; |
| 39 | let lastPhaseName = ''; |
| 40 | let lastPercent = -1; |
| 41 | let lastCount = 0; |
| 42 | |
| 43 | // The persistent "phase done" lines — the ones that stay in scrollback — |
| 44 | // are printed HERE, on the main thread, not by the worker. process.stdout |
| 45 | // reaches a Windows console through the wide-char API, so these lines can |
| 46 | // carry the same Unicode glyphs @clack/prompts draws around them (#398); |
| 47 | // the worker's raw fs.writeSync path can't (codepage mojibake, #168) and is |
| 48 | // now used only for the transient, self-erasing animation frames. The main |
| 49 | // thread is guaranteed alive here: phase changes arrive via its own |
| 50 | // progress callback. |
| 51 | const printPhaseDone = (): void => { |
| 52 | if (!lastPhaseName) return; |
| 53 | let detail = ''; |
| 54 | if (lastPercent >= 0) detail = ` ${G.dash} done`; |
| 55 | else if (lastCount > 0) detail = ` ${G.dash} ${lastCount.toLocaleString()} found`; |
| 56 | // Leading \r + erase clears the worker's in-flight animation line; one |
| 57 | // atomic write so a worker frame can't interleave mid-line. |
| 58 | process.stdout.write( |
| 59 | `\r\x1b[K${DM}${G.rail}${RST} ${GRN}${G.phaseDone}${RST} ${lastPhaseName}${detail}\n` |
| 60 | ); |
| 61 | lastPhaseName = ''; |
| 62 | lastPercent = -1; |
| 63 | lastCount = 0; |
| 64 | }; |
| 65 | |
| 66 | const workerPath = path.join(__dirname, 'shimmer-worker.js'); |
| 67 | const worker = new Worker(workerPath, { |
| 68 | // colors:false keeps the animation (still an interactive TTY) but drops |
| 69 | // the ANSI color codes, honoring NO_COLOR / --no-color (#1281). |
| 70 | workerData: { startTime: Date.now(), colors: useColor }, |
| 71 | }); |
| 72 | |
| 73 | return { |
| 74 | onProgress(progress: IndexProgress) { |
| 75 | const phaseName = PHASE_NAMES[progress.phase] || progress.phase; |
| 76 | |
| 77 | if (progress.phase !== lastPhase && lastPhase) { |
| 78 | printPhaseDone(); |
| 79 | } |
| 80 | lastPhase = progress.phase; |
| 81 | lastPhaseName = phaseName; |
| 82 |
no test coverage detected