[READ-ONLY] Mirror of https://github.com/bombshell-dev/clack. Effortlessly build beautiful command-line apps bomb.sh/docs/clack/basics/getting-started/
cli command-line command-line-app node prompt prompts
5

Configure Feed

Select the types of activity you want to include in your feed.

feat(@clack/prompts): adapt `spinner` to CI environment (#169)

Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>

authored by

Bruno Rocha
Nate Moore
and committed by
GitHub
(Dec 13, 2024, 11:59 PM -0600) f9f139de a0e28acb

+70 -10
+5
.changeset/thin-moose-tease.md
··· 1 + --- 2 + '@clack/prompts': patch 3 + --- 4 + 5 + Adapts `spinner` output for static CI environments
+2 -1
examples/basic/package.json
··· 10 10 }, 11 11 "scripts": { 12 12 "start": "jiti ./index.ts", 13 - "spinner": "jiti ./spinner.ts" 13 + "spinner": "jiti ./spinner.ts", 14 + "spinner-ci": "npx cross-env CI=\"true\" jiti ./spinner-ci.ts" 14 15 }, 15 16 "devDependencies": { 16 17 "jiti": "^1.17.0"
+36
examples/basic/spinner-ci.ts
··· 1 + /** 2 + * This example addresses a issue reported in GitHub Actions where `spinner` was excessively writing messages, 3 + * leading to confusion and cluttered output. 4 + * To enhance the CI workflow and provide a smoother experience, 5 + * the following changes have been made only for CI environment: 6 + * - Messages will now only be written when a `spinner` method is called and the message updated, preventing unnecessary message repetition. 7 + * - There will be no loading dots animation, instead it will be always `...` 8 + * - Instead of erase the previous message, action that is blocked during CI, it will just write a new one. 9 + * 10 + * Issue: https://github.com/natemoo-re/clack/issues/168 11 + */ 12 + import * as p from '@clack/prompts'; 13 + 14 + const s = p.spinner(); 15 + let progress = 0; 16 + let counter = 0; 17 + let loop: NodeJS.Timer; 18 + 19 + p.intro('Running spinner in CI environment'); 20 + s.start('spinner.start'); 21 + new Promise((resolve) => { 22 + loop = setInterval(() => { 23 + if (progress % 1000 === 0) { 24 + counter++; 25 + } 26 + progress += 100; 27 + s.message(`spinner.message [${counter}]`); 28 + if (counter > 6) { 29 + clearInterval(loop); 30 + resolve(true); 31 + } 32 + }, 100); 33 + }).then(() => { 34 + s.stop('spinner.stop'); 35 + p.outro('Done'); 36 + });
+1 -1
examples/basic/spinner.ts
··· 3 3 p.intro('spinner start...'); 4 4 5 5 const spin = p.spinner(); 6 - const total = 10000; 6 + const total = 6000; 7 7 let progress = 0; 8 8 spin.start(); 9 9
+25 -8
packages/prompts/src/index.ts
··· 643 643 export const spinner = () => { 644 644 const frames = unicode ? ['◒', '◐', '◓', '◑'] : ['•', 'o', 'O', '0']; 645 645 const delay = unicode ? 80 : 120; 646 + const isCI = process.env.CI === 'true'; 646 647 647 648 let unblock: () => void; 648 649 let loop: NodeJS.Timeout; 649 650 let isSpinnerActive = false; 650 651 let _message = ''; 652 + let _prevMessage: string | undefined = undefined; 651 653 652 654 const handleExit = (code: number) => { 653 655 const msg = code > 1 ? 'Something went wrong' : 'Canceled'; ··· 676 678 process.removeListener('exit', handleExit); 677 679 }; 678 680 681 + 682 + const clearPrevMessage = () => { 683 + if (_prevMessage === undefined) return; 684 + if (isCI) process.stdout.write('\n'); 685 + const prevLines = _prevMessage.split('\n'); 686 + process.stdout.write(cursor.move(-999, prevLines.length - 1)); 687 + process.stdout.write(erase.down(prevLines.length)); 688 + }; 689 + 690 + const parseMessage = (msg: string): string => { 691 + return msg.replace(/\.+$/, ''); 692 + }; 693 + 679 694 const start = (msg = ''): void => { 680 695 isSpinnerActive = true; 681 696 unblock = block(); 682 - _message = msg.replace(/\.+$/, ''); 697 + _message = parseMessage(msg); 683 698 process.stdout.write(`${color.gray(S_BAR)}\n`); 684 699 let frameIndex = 0; 685 700 let dotsTimer = 0; 686 701 registerHooks(); 687 702 loop = setInterval(() => { 703 + if (isCI && _message === _prevMessage) { 704 + return; 705 + } 706 + clearPrevMessage(); 707 + _prevMessage = _message; 688 708 const frame = color.magenta(frames[frameIndex]); 689 - const loadingDots = '.'.repeat(Math.floor(dotsTimer)).slice(0, 3); 690 - process.stdout.write(cursor.move(-999, 0)); 691 - process.stdout.write(erase.down(1)); 709 + const loadingDots = isCI ? '...' : '.'.repeat(Math.floor(dotsTimer)).slice(0, 3); 692 710 process.stdout.write(`${frame} ${_message}${loadingDots}`); 693 711 frameIndex = frameIndex + 1 < frames.length ? frameIndex + 1 : 0; 694 712 dotsTimer = dotsTimer < frames.length ? dotsTimer + 0.125 : 0; ··· 696 714 }; 697 715 698 716 const stop = (msg = '', code = 0): void => { 699 - _message = msg ?? _message; 700 717 isSpinnerActive = false; 701 718 clearInterval(loop); 719 + clearPrevMessage(); 702 720 const step = 703 721 code === 0 704 722 ? color.green(S_STEP_SUBMIT) 705 723 : code === 1 706 724 ? color.red(S_STEP_CANCEL) 707 725 : color.red(S_STEP_ERROR); 708 - process.stdout.write(cursor.move(-999, 0)); 709 - process.stdout.write(erase.down(1)); 726 + _message = parseMessage(msg ?? _message); 710 727 process.stdout.write(`${step} ${_message}\n`); 711 728 clearHooks(); 712 729 unblock(); 713 730 }; 714 731 715 732 const message = (msg = ''): void => { 716 - _message = msg ?? _message; 733 + _message = parseMessage(msg ?? _message); 717 734 }; 718 735 719 736 return {
+1
pnpm-lock.yaml
··· 5369 5369 yocto-queue@0.1.0: {} 5370 5370 5371 5371 yocto-queue@1.1.1: {} 5372 +