├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── README.md ├── index.d.ts ├── lib ├── cursor.js ├── index.js └── utilities.js ├── package-lock.json ├── package.json ├── renovate.json └── test └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [*.less] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [Makefile] 20 | indent_style = tab 21 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # github: [jaywcjlove] 4 | # patreon: # Replace with a single Patreon username 5 | # open_collective: # Replace with a single Open Collective username 6 | # ko_fi: # Replace with a single Ko-fi username 7 | # tidelift: npm/xxx 8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | custom: https://jaywcjlove.github.io/#/sponsor 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | website: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | id-token: write 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 18 18 | registry-url: 'https://registry.npmjs.org' 19 | 20 | # - run: mkdir -p build 21 | # - run: npm i markdown-to-html-cli -g 22 | # - run: markdown-to-html --output build/index.html 23 | 24 | - name: Create idoc config. 25 | run: | 26 | cat > idoc.yml << EOF 27 | site: "Image To URI {{version}}" 28 | menus: 29 | Home: index.html 30 | Sponsor: https://jaywcjlove.github.io/#/sponsor 31 | EOF 32 | 33 | - run: npm install idoc@1.27.2 -g 34 | - run: idoc --output="build" 35 | 36 | - name: Generate Contributors Images 37 | uses: jaywcjlove/github-action-contributors@main 38 | with: 39 | filter-author: (renovate\[bot\]|renovate-bot|dependabot\[bot\]) 40 | output: build/CONTRIBUTORS.svg 41 | avatarSize: 42 42 | 43 | - name: Create Tag 44 | id: create_tag 45 | uses: jaywcjlove/create-tag-action@main 46 | 47 | - name: get tag version 48 | id: tag_version 49 | uses: jaywcjlove/changelog-generator@main 50 | 51 | - name: Deploy 52 | uses: peaceiris/actions-gh-pages@v3 53 | with: 54 | github_token: ${{ secrets.GITHUB_TOKEN }} 55 | publish_dir: ./build 56 | 57 | - name: Generate Changelog 58 | id: changelog 59 | uses: jaywcjlove/changelog-generator@main 60 | with: 61 | head-ref: ${{steps.create_tag.outputs.version}} 62 | filter-author: (renovate-bot|Renovate Bot) 63 | filter: '[R|r]elease[d]\s+[v|V]\d(\.\d+){0,2}' 64 | 65 | - name: Create Release 66 | uses: jaywcjlove/create-tag-action@main 67 | with: 68 | package-path: ./package.json 69 | release: true 70 | body: | 71 | [![Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-048754?logo=buymeacoffee)](https://jaywcjlove.github.io/#/sponsor) [![](https://img.shields.io/badge/Open%20in-unpkg-blue)](https://uiwjs.github.io/npm-unpkg/#/pkg/loading-cli@${{steps.changelog.outputs.version}}/file/README.md) 72 | 73 | Documentation ${{ steps.changelog.outputs.tag }}: https://raw.githack.com/uiwjs/react-md-editor/${{ steps.changelog.outputs.gh-pages-short-hash }}/index.html 74 | Comparing Changes: ${{ steps.changelog.outputs.compareurl }} 75 | 76 | ```bash 77 | npm i loading-cli@${{steps.changelog.outputs.version}} 78 | ``` 79 | 80 | ${{ steps.changelog.outputs.changelog }} 81 | 82 | - run: npm publish --access public --provenance 83 | name: 📦 loading-cli publish to NPM 84 | continue-on-error: true 85 | env: 86 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # loading-cli 2 | 3 | [![Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-048754?logo=buymeacoffee)](https://jaywcjlove.github.io/#/sponsor) 4 | [![](https://jaywcjlove.github.io/sb/ico/npm.svg)](https://www.npmjs.com/package/loading-cli) 5 | [![CI](https://github.com/jaywcjlove/loading-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/jaywcjlove/loading-cli/actions/workflows/ci.yml) 6 | 7 | Terminal loading effect. 8 | 9 | 10 | 11 | # Install 12 | 13 | ```bash 14 | $ npm install --save loading-cli 15 | ``` 16 | 17 | # Usage 18 | 19 | ```js 20 | const loading = require('loading-cli'); 21 | const load = loading("loading text!!").start() 22 | 23 | setTimeout(function(){ 24 | load.color = 'yellow'; 25 | load.text = ' Loading rainbows'; 26 | },2000) 27 | 28 | // stop 29 | setTimeout(function(){ 30 | load.stop() 31 | },3000) 32 | ``` 33 | 34 | Custom text color [colors-cli](https://github.com/jaywcjlove/colors-cli) 35 | 36 | ```js 37 | const color = require('colors-cli/toxic'); 38 | const loading = require('loading-cli'); 39 | 40 | const load = loading("loading text!!".blue).start(); 41 | // stop 42 | setTimeout(function(){ 43 | load.stop() 44 | },3000) 45 | ``` 46 | 47 | # API 48 | 49 | ## loading([options|text]) 50 | 51 | ### options 52 | 53 | ```js 54 | load({ 55 | "text":"loading text!!", 56 | "color":"yellow", 57 | "interval":100, 58 | "stream": process.stdout, 59 | "frames":["◰", "◳", "◲", "◱"] 60 | }) 61 | ``` 62 | 63 | ### text 64 | 65 | Type: string 66 | Text to display after the spinner. 67 | 68 | ```js 69 | loading("loading text!!") 70 | ``` 71 | 72 | ## color 73 | 74 | Values:`black` `red` `green` `yellow` `blue` `magenta` `cyan` `white` `gray` 75 | 76 | 77 | ## frames 78 | 79 | ```bash 80 | ["◰", "◳", "◲", "◱"] 81 | ["◐", "◓", "◑", "◒"] 82 | [".", "o", "O", "°", "O", "o", "."] 83 | ["⊶", "⊷"] 84 | ["ဝ", "၀"] 85 | ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"] 86 | ["🕐 ", "🕑 ", "🕒 ", "🕓 ", "🕔 ", "🕕 ", "🕖 ", "🕗 ", "🕘 ", "🕙 ", "🕚 "] 87 | ``` 88 | 89 | # Instance 90 | 91 | ## .start([text]) 92 | 93 | Start the spinner. Returns the instance. 94 | 95 | ## .stop() 96 | 97 | Stop and clear the spinner. Returns the instance. 98 | 99 | ## .clear() 100 | 101 | Clear the spinner. Returns the instance. 102 | 103 | ## .succeed([text]) 104 | 105 | Stop the spinner, change it to a green `✔` and persist the current text, or text if provided. Returns the instance. See the GIF below. 106 | 107 | ## .fail([text]) 108 | 109 | Stop the spinner, change it to a red `✖` and persist the current text, or text if provided. Returns the instance. See the GIF below. 110 | 111 | ## .warn([text]) 112 | 113 | Stop the spinner, change it to a yellow `⚠` and persist the current text, or text if provided. Returns the instance. 114 | 115 | ## .info([text]) 116 | 117 | Stop the spinner, change it to a blue `ℹ` and persist the current text, or text if provided. Returns the instance. 118 | 119 | ## .render() 120 | 121 | Manually render a new frame. Returns the instance. 122 | 123 | ## .frame() 124 | 125 | Get a new frame. 126 | 127 | ```js 128 | const loading = require('loading-cli'); 129 | const load = loading("loading text!!"); 130 | load.frame(["◰", "◳", "◲", "◱"]); 131 | load.start(); 132 | ``` 133 | 134 | ## .text 135 | 136 | Change the text. 137 | 138 | ## .color 139 | 140 | Change the spinner color. 141 | 142 | ## Contributors 143 | 144 | As always, thanks to our amazing contributors! 145 | 146 | 147 | 148 | 149 | 150 | Made with [contributors](https://github.com/jaywcjlove/github-action-contributors). 151 | 152 | ## License 153 | 154 | Licensed under the MIT License. 155 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare namespace loading { 4 | interface Options { 5 | text?: string; 6 | color?: string; 7 | interval?: number; 8 | stream?: NodeJS.WritableStream; 9 | frames?: string[]; 10 | } 11 | interface Loading { 12 | /** 13 | * Change the text after the spinner. 14 | */ 15 | text: string; 16 | /** 17 | * Change the spinner color. 18 | */ 19 | color: string; 20 | /** 21 | * Start the spinner. 22 | * @param text - Set the current text. 23 | */ 24 | start(text?: string): Loading; 25 | /** 26 | * Stop and clear the spinner. 27 | * @returns The spinner instance. 28 | */ 29 | stop(): Loading; 30 | /** 31 | * Clear the spinner. 32 | * @returns The spinner instance. 33 | */ 34 | clear(): Loading; 35 | /** 36 | * Stop the spinner, change it to a green `✔` and persist the current text, or `text` if provided. 37 | * @param text - Will persist text if provided. 38 | * @returns The spinner instance. 39 | */ 40 | succeed(text?: string): Loading; 41 | 42 | /** 43 | * Stop the spinner, change it to a red `✖` and persist the current text, or `text` if provided. 44 | * @param text - Will persist text if provided. 45 | * @returns The spinner instance. 46 | */ 47 | fail(text?: string): Loading; 48 | /** 49 | * Stop the spinner, change it to a yellow `⚠` and persist the current text, or `text` if provided. 50 | * @param text - Will persist text if provided. 51 | * @returns The spinner instance. 52 | */ 53 | warn(text?: string): Loading; 54 | 55 | /** 56 | * Stop the spinner, change it to a blue `ℹ` and persist the current text, or `text` if provided. 57 | * @param text - Will persist text if provided. 58 | * @returns The spinner instance. 59 | */ 60 | info(text?: string): Loading; 61 | 62 | /** 63 | * Manually render a new frame. 64 | * @returns The spinner instance. 65 | */ 66 | render(): Loading; 67 | /** 68 | * Get a new frame. 69 | * @returns The spinner instance text. 70 | */ 71 | frame(): string; 72 | } 73 | } 74 | 75 | declare const loading: { 76 | (options?: loading.Options | string): loading.Loading; 77 | } 78 | 79 | 80 | export = loading; 81 | -------------------------------------------------------------------------------- /lib/cursor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ANSI控制码的说明 3 | * 4 | * 33[0m 关闭所有属性 5 | * 33[1m 设置高亮度 6 | * 33[4m 下划线 7 | * 33[5m 闪烁 8 | * 33[7m 反显 9 | * 33[8m 消隐 10 | * 33[30m -- 33[37m 设置前景色 11 | * 33[40m -- 33[47m 设置背景色 12 | * 33[nA 光标上移n行 13 | * 33[nB 光标下移n行 14 | * 33[nC 光标右移n行 15 | * 33[nD 光标左移n行 16 | * 33[y;xH设置光标位置 17 | * 33[2J 清屏 18 | * 33[K 清除从光标到行尾的内容 19 | * 33[s 保存光标位置 20 | * 33[u 恢复光标位置 21 | * 33[?25l 隐藏光标 22 | * 33[?25h 显示光标 23 | */ 24 | var hidden = false; 25 | exports.show = function (stream) { 26 | const s = stream || process.stderr; 27 | if (!s.isTTY) { 28 | return; 29 | } 30 | hidden = false; 31 | s.write('\u001b[?25h'); 32 | }; 33 | exports.hide = function (stream) { 34 | var s = stream || process.stderr; 35 | if (!s.isTTY) { 36 | return; 37 | } 38 | hidden = true; 39 | s.write('\u001b[?25l'); 40 | }; 41 | exports.toggle = function (force) { 42 | if (force !== undefined) { 43 | hidden = force; 44 | } 45 | if (hidden) { 46 | exports.show(); 47 | } else { 48 | exports.hide(); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var ut = require('./utilities'); 2 | var cursor = require('./cursor'); 3 | var color = require('colors-cli') 4 | 5 | function loading(options) { 6 | if (!(this instanceof loading)) { 7 | return new loading(options) 8 | } 9 | if (typeof options === 'string') { 10 | options = { 11 | text: options 12 | } 13 | } 14 | 15 | this.options = ut.extend(options, { 16 | text: '', 17 | color: 'cyan', 18 | stream: process.stderr, 19 | // stream: process.stdout 20 | // loading 样式 21 | frames: ["◜", "◠", "◝", "◞", "◡", "◟"] 22 | }); 23 | 24 | // 文本显示 25 | this.text = this.options.text; 26 | 27 | // 颜色显示 28 | this.color = this.options.color; 29 | 30 | // 动画间隔时间 31 | this.interval = this.options.interval || 60; 32 | this.stream = this.options.stream; 33 | 34 | // loading 样式 35 | this.frames = this.options.frames; 36 | 37 | // 不存在 38 | this.id = null; 39 | 40 | // 要检查 Node 是否正在运行一个 TTY上下文 中 41 | // linux 中没有运行在 tty 下的进程是 守护进程 42 | this.enabled = this.options.enabled || ((this.stream && this.stream.isTTY) && !process.env.CI); 43 | this.frameIndex = 0; 44 | } 45 | 46 | loading.prototype.frame = function (frame) { 47 | if (frame) this.options.frames = frame; 48 | var frames = this.options.frames; 49 | // var frames = ["◜", "◠", "◝", "◞", "◡", "◟"]; 50 | // var frames = ["◰", "◳", "◲", "◱"] 51 | // var frames = ["◐", "◓", "◑", "◒"] 52 | // var frames = [".", "o", "O", "°", "O", "o", "."] 53 | // var frames = ["⊶", "⊷"] 54 | // var frames = ["ဝ", "၀"] 55 | // var frames = ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"] 56 | // var frames = ["🕐 ", "🕑 ", "🕒 ", "🕓 ", "🕔 ", "🕕 ", "🕖 ", "🕗 ", "🕘 ", "🕙 ", "🕚 "] 57 | var frame = frames[this.frameIndex]; 58 | if (this.color) { 59 | frame = color[this.color](frame); 60 | } 61 | this.frameIndex = ++this.frameIndex % frames.length; 62 | return frame + ' ' + this.text; 63 | } 64 | 65 | loading.prototype.clear = function () { 66 | if (!this.enabled) { 67 | return this; 68 | } 69 | this.stream.clearLine(); 70 | this.stream.cursorTo(0); 71 | return this; 72 | } 73 | 74 | loading.prototype.render = function () { 75 | this.clear(); 76 | this.stream.write(this.frame()); 77 | return this; 78 | } 79 | 80 | loading.prototype.start = function (text) { 81 | if (text) this.text = text; 82 | if (!this.enabled || this.id) return this; 83 | this.clear(); 84 | cursor.hide(this.stream); 85 | this.id = setInterval(this.render.bind(this), this.interval); 86 | return this; 87 | } 88 | 89 | loading.prototype.stop = function () { 90 | if (!this.enabled) return this; 91 | clearInterval(this.id); 92 | this.id = null; 93 | this.clear(); 94 | cursor.show(this.stream); 95 | return this; 96 | } 97 | 98 | loading.prototype.succeed = function (text) { 99 | return this.stopAndPersist(color.green('✔'), text); 100 | } 101 | loading.prototype.fail = function (text) { 102 | return this.stopAndPersist(color.red('✖'), text); 103 | } 104 | loading.prototype.warn = function (text) { 105 | return this.stopAndPersist(color.yellow('⚠'), text); 106 | } 107 | loading.prototype.info = function (text) { 108 | return this.stopAndPersist(color.blue('ℹ'), text); 109 | } 110 | loading.prototype.stopAndPersist = function (symbol, text) { 111 | text = text || this.text 112 | this.stop(); 113 | this.stream.write((symbol ? symbol + ' ' : ' ') + text + '\n'); 114 | return this; 115 | } 116 | 117 | module.exports = loading 118 | -------------------------------------------------------------------------------- /lib/utilities.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extend:extend 3 | } 4 | 5 | // 合并对象 6 | function extend(des, src, override){ 7 | if(src instanceof Array){ 8 | for(var i = 0, len = src.length; i < len; i++) 9 | extend(des, src[i], override); 10 | } 11 | for( var i in src){ 12 | if(override || !(i in des)){ 13 | des[i] = src[i]; 14 | } 15 | } 16 | return des; 17 | } 18 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "loading-cli", 3 | "version": "1.1.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "colors-cli": { 8 | "version": "1.0.33", 9 | "resolved": "https://registry.npmjs.org/colors-cli/-/colors-cli-1.0.33.tgz", 10 | "integrity": "sha512-PWGsmoJFdOB0t+BeHgmtuoRZUQucOLl5ii81NBzOOGVxlgE04muFNHlR5j8i8MKbOPELBl3243AI6lGBTj5ICQ==" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "loading-cli", 3 | "version": "1.1.2", 4 | "description": "Terminal loading effect.", 5 | "homepage": "http://jaywcjlove.github.io/loading-cli", 6 | "funding": "https://jaywcjlove.github.io/#/sponsor", 7 | "main": "lib/index.js", 8 | "scripts": { 9 | "test": "node test/test.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/jaywcjlove/loading-cli" 14 | }, 15 | "keywords": [ 16 | "command", 17 | "loading-cli", 18 | "loading", 19 | "exit", 20 | "quit", 21 | "process", 22 | "graceful", 23 | "shutdown", 24 | "sigterm", 25 | "sigint", 26 | "cli", 27 | "cursor", 28 | "ansi", 29 | "term", 30 | "terminal", 31 | "console", 32 | "shell", 33 | "command-line" 34 | ], 35 | "files": [ 36 | "lib", 37 | "index.d.ts" 38 | ], 39 | "author": "kenny wang ", 40 | "license": "MIT", 41 | "dependencies": { 42 | "colors-cli": "^1.0.26" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var load = require('../'); 2 | var st = load({ 3 | "text":"loading text!!" 4 | }) 5 | 6 | st.start() 7 | 8 | setTimeout(function(){ 9 | // st.stop() 10 | st.frame(["◰", "◳", "◲", "◱"]); 11 | st.text = 'Loading rainbows'; 12 | },1000) 13 | 14 | setTimeout(function(){ 15 | st.stop() 16 | },3000) 17 | 18 | --------------------------------------------------------------------------------