├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── funding.yml └── pull_request_template.md ├── .gitignore ├── .npmrc ├── .travis.yml ├── cli.js ├── code-of-conduct.md ├── contributing.md ├── index.js ├── license.md ├── media └── header.png ├── package.json ├── readme.md ├── snapcraft.yaml └── src ├── help.js └── rels.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [{*.json, *.yml}] 15 | indent_style = space 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior. 12 | 13 | **Expected behavior** 14 | A clear and concise description of what you expected to happen. 15 | 16 | **Screenshots** 17 | If applicable, add screenshots to help explain your problem. 18 | 19 | **Technical Info (please complete the following information)** 20 | - OS: 21 | - Rels Version: 22 | - Node.js Version: 23 | 24 | **Additional context** 25 | Add any other context about the problem here. 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Additional context** 14 | Add any other context or screenshots about the feature request here. 15 | -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- 1 | github: klaussinani 2 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | yarn.lock 4 | 5 | # logs 6 | *.log 7 | 8 | # OS 9 | .DS_Store 10 | 11 | # IDE 12 | .vscode 13 | .idea 14 | *.swp 15 | *.swo -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 12 4 | - 10 5 | - 8 6 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | const meow = require('meow'); 4 | const updateNotifier = require('update-notifier'); 5 | const help = require('./src/help'); 6 | const pkg = require('./package.json'); 7 | const rels = require('.'); 8 | 9 | const cli = meow(help, { 10 | flags: { 11 | all: { 12 | type: 'boolean', 13 | alias: 'a' 14 | }, 15 | help: { 16 | type: 'boolean', 17 | alias: 'h' 18 | }, 19 | list: { 20 | type: 'string', 21 | alias: 'l' 22 | }, 23 | repo: { 24 | type: 'string', 25 | alias: 'r' 26 | }, 27 | version: { 28 | type: 'boolean', 29 | alias: 'v' 30 | } 31 | } 32 | }); 33 | 34 | updateNotifier({pkg}).notify(); 35 | 36 | rels(cli.flags); 37 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at klaussinani@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Rels 2 | 3 | Thank you for taking the time to contribute to Rels! 4 | 5 | Please note that this project is released with a [Contributor Code of Conduct](code-of-conduct.md). By participating in this project you agree to abide by its terms. 6 | 7 | ## How to contribute 8 | 9 | ### Improve documentation 10 | 11 | Typo corrections, error fixes, better explanations, more examples etc. Open an issue regarding anything that you think it could be improved! You can use the [`docs` label](https://github.com/klaussinani/rels/labels/docs) to find out what others have suggested! 12 | 13 | ### Improve issues 14 | 15 | Sometimes reported issues lack information, are not reproducible, or are even plain invalid. Help us out to make them easier to resolve. Handling issues takes a lot of time that we could rather spend on fixing bugs and adding features. 16 | 17 | ### Give feedback on issues 18 | 19 | We're always looking for more opinions on discussions in the issue tracker. It's a good opportunity to influence the future direction of the project. 20 | 21 | The [`question` label](https://github.com/klaussinani/rels/labels/question) is a good place to find ongoing discussions. 22 | 23 | ### Write code 24 | 25 | You can use issue labels to discover issues you could help us out with! 26 | 27 | - [`feature request` issues](https://github.com/klaussinani/rels/labels/feature%20request) are features we are open to including 28 | - [`bug` issues](https://github.com/klaussinani/rels/labels/bug) are known bugs we would like to fix 29 | - [`future` issues](https://github.com/klaussinani/rels/labels/future) are those that we'd like to get to, but not anytime soon. Please check before working on these since we may not yet want to take on the burden of supporting those features 30 | - on the [`help wanted`](https://github.com/klaussinani/rels/labels/help%20wanted) label you can always find something exciting going on 31 | 32 | You may find an issue is assigned, or has the [`assigned` label](https://github.com/klaussinani/rels/labels/assigned). Please double-check before starting on this issue because somebody else is likely already working on it 33 | 34 | ### Say hi 35 | 36 | Come over and say hi anytime you feel like on [Gitter](https://gitter.im/klaussinani/rels). 37 | 38 | ### Translating Documentation 39 | 40 | #### Create a Translation 41 | 42 | - Visit the [`translations' index`](https://github.com/klaussinani/rels/tree/master/docs/readme.md) file to ensure that the document is not already translated in your target language. 43 | - Add the name of the language to the document as an extension, e.g: `readme.JP.md` 44 | - Place the translated document inside the [`docs`](https://github.com/klaussinani/rels/tree/master/docs) directory. 45 | - Add your github profile and the translated document at the [`translations' index`](https://github.com/klaussinani/rels/tree/master/docs/readme.md) file. 46 | - Create a Pull Request including the language in the title, e.g: `Readme: Japanese Translation` 47 | 48 | #### Improve a Translation 49 | 50 | - Include your github profile next to the translation you improved at the [`translations' index`](https://github.com/klaussinani/rels/tree/master/docs/readme.md) file. 51 | - Create a Pull Request that delivers the improvements and include the language in the title, e.g: `Readme: Improvements for the Japanese Translation` 52 | 53 | ### Submitting an issue 54 | 55 | - Search the issue tracker before opening an issue 56 | - Ensure you're using the latest version of Rels 57 | - Use a descriptive title 58 | - Include as much information as possible; 59 | - Steps to reproduce the issue 60 | - Error message 61 | - Rels version 62 | - Operating system **etc** 63 | 64 | ### Submitting a pull request 65 | 66 | - Non-trivial changes are often best discussed in an issue first, to prevent you from doing unnecessary work 67 | - Try making the pull request from a [topic branch](https://github.com/dchelimsky/rspec/wiki/Topic-Branches) if it is of crucial importance 68 | - Use a descriptive title for the pull request and commits 69 | - You might be asked to do changes to your pull request, you can do that by just [updating the existing one](https://github.com/RichardLitt/docs/blob/master/amending-a-commit-guide.md) 70 | 71 | > Based on project [AVA](https://github.com/avajs/ava/blob/master/contributing.md)'s contributing.md 72 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | const rels = require('./src/rels'); 4 | 5 | const {log} = console; 6 | 7 | const relsCLI = flags => { 8 | if (!flags.repo) { 9 | const message = [ 10 | 'No repository was given as input.', 11 | 'Run "rels --help" for more information and examples.' 12 | ].join('\n'); 13 | 14 | return log(message); 15 | } 16 | 17 | return rels.init(flags.repo, flags.all ? Infinity : flags.list); 18 | }; 19 | 20 | module.exports = relsCLI; 21 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Klaus Sinani (klaussinani.github.io) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /media/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/rels/6a8fa0d51cc8ff9581788dda4fb2175d181b3345/media/header.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rels", 3 | "version": "0.4.3", 4 | "description": "Github release analytics for the console", 5 | "license": "MIT", 6 | "repository": "klaussinani/rels", 7 | "author": { 8 | "name": "Klaus Sinani", 9 | "email": "klaussinani@gmail.com", 10 | "url": "https://klaussinani.github.io" 11 | }, 12 | "bin": { 13 | "rels": "cli.js" 14 | }, 15 | "engines": { 16 | "node": ">=8" 17 | }, 18 | "files": [ 19 | "src", 20 | "index.js", 21 | "cli.js" 22 | ], 23 | "keywords": [ 24 | "github", 25 | "releases", 26 | "analytics", 27 | "console", 28 | "cli" 29 | ], 30 | "scripts": { 31 | "test": "xo" 32 | }, 33 | "devDependencies": { 34 | "xo": "*" 35 | }, 36 | "xo": { 37 | "space": 2, 38 | "rule": { 39 | "unicorn/string-content": 0 40 | } 41 | }, 42 | "dependencies": { 43 | "meow": "^5.0.0", 44 | "update-notifier": "^2.5.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 | Rels 3 |

4 | 5 |

6 | Github release analytics for the console 7 |

8 | 9 |
10 | 11 | Rels 12 | 13 |
14 | 15 |

16 | 17 | Build Status 18 | 19 |

20 | 21 | ## Description 22 | 23 | By utilizing a simple and minimal usage syntax rels enables you to easily view various analytics & stats regarding the releases of any GitHub repository, displayed in a clean & concise manner, right from within your terminal. 24 | 25 | You can now support the development process through [GitHub Sponsors](https://github.com/sponsors/klaussinani). 26 | 27 | Visit the [contributing guidelines](https://github.com/klaussinani/rels/blob/master/contributing.md#translating-documentation) to learn more on how to translate this document into more languages. 28 | 29 | Come over to [Twitter](https://twitter.com/klaussinani) to share your thoughts on the project. 30 | 31 | ## Highlights 32 | 33 | - Overall release analytics 34 | - Clean & concise output 35 | - Simple & minimal usage syntax 36 | - Update notifications 37 | 38 | ## Contents 39 | 40 | - [Description](#description) 41 | - [Highlights](#highlights) 42 | - [Install](#install) 43 | - [Usage](#usage) 44 | - [Development](#development) 45 | - [Related](#related) 46 | - [Team](#team) 47 | - [License](#license) 48 | 49 | ## Install 50 | 51 | ### Yarn 52 | 53 | ```bash 54 | yarn global add rels 55 | ``` 56 | 57 | ### NPM 58 | 59 | ```bash 60 | npm install --global rels 61 | ``` 62 | 63 | ### Snapcraft 64 | 65 | ```bash 66 | snap install rels 67 | ``` 68 | 69 | ## Usage 70 | 71 | ``` 72 | Usage 73 | $ rels [ ...] 74 | 75 | Options 76 | --repo, -r Repository to get analytics for 77 | --list, -l Number of releases to be displayed 78 | --all, -a Display all releases 79 | --help, -h Display help message 80 | --version, -v Display installed version 81 | 82 | Examples 83 | $ rels --repo klaussinani/tusk 84 | $ rels --repo klaussinani/tusk --all 85 | $ rels --repo klaussinani/tusk --list 3 86 | ``` 87 | 88 | ## Development 89 | 90 | For more info on how to contribute to the project, please read the [contributing guidelines](https://github.com/klaussinani/rels/blob/master/contributing.md). 91 | 92 | - Fork the repository and clone it to your machine 93 | - Navigate to your local fork: `cd rels` 94 | - Install the project dependencies: `npm install` or `yarn install` 95 | - Lint the code for errors: `npm test` or `yarn test` 96 | 97 | ## Related 98 | 99 | - [signale](https://github.com/klaussinani/signale) - Highly configurable logging utility 100 | - [qoa](https://github.com/klaussinani/qoa) - Minimal interactive command-line prompts 101 | - [taskbook](https://github.com/klaussinani/taskbook) - Tasks, boards & notes for the command-line habitat 102 | - [hyperocean](https://github.com/klaussinani/hyperocean) - Deep oceanic blue Hyper terminal theme 103 | 104 | ## Team 105 | 106 | - Klaus Sinani [(@klaussinani)](https://github.com/klaussinani) 107 | 108 | ## License 109 | 110 | [MIT](https://github.com/klaussinani/rels/blob/master/license.md) 111 | -------------------------------------------------------------------------------- /snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: rels 2 | version: '0.0.0' 3 | version-script: git describe --always | cut -c 2- 4 | summary: Github release analytics for the console 5 | description: | 6 | By utilizing a simple and minimal usage syntax rels enables you to easily view various 7 | analytics and stats regarding the releases of any GitHub repository, displayed in a clean 8 | and concise manner, right from within your terminal. 9 | 10 | grade: stable 11 | confinement: strict 12 | 13 | architectures: 14 | - amd64 15 | - i386 16 | - armhf 17 | 18 | apps: 19 | rels: 20 | command: rels 21 | plugs: 22 | - home 23 | - network 24 | - network-control 25 | 26 | parts: 27 | rels: 28 | plugin: nodejs 29 | node-engine: 12.16.2 30 | node-package-manager: yarn 31 | source: . 32 | -------------------------------------------------------------------------------- /src/help.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = ` 4 | Usage 5 | $ rels [ ...] 6 | 7 | Options 8 | --repo, -r Repository to get analytics for 9 | --list, -l Number of releases to be displayed 10 | --all, -a Display all releases 11 | --help, -h Display help message 12 | --version, -v Display installed version 13 | 14 | Examples 15 | $ rels --repo klaussinani/tusk 16 | $ rels --repo klaussinani/tusk --all 17 | $ rels --repo klaussinani/tusk --list 3 18 | `; 19 | -------------------------------------------------------------------------------- /src/rels.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {get} = require('https'); 3 | const pkg = require('./../package.json'); 4 | 5 | const {log} = console; 6 | 7 | class Rels { 8 | constructor() { 9 | this._latestRelease = {}; 10 | this._padding = new Array(6).join(' '); 11 | } 12 | 13 | get _opts() { 14 | return { 15 | host: 'api.github.com', 16 | headers: { 17 | 'user-agent': `${pkg.repository} - ${process.title}` 18 | } 19 | }; 20 | } 21 | 22 | get _path() { 23 | return { 24 | releases: x => `/repos/${x}/releases`, 25 | latest: x => `/repos/${x}/releases/latest` 26 | }; 27 | } 28 | 29 | get _latest() { 30 | return { 31 | date: this._format.date.long(this._latestRelease.created_at), 32 | tag: this._latestRelease.tag_name 33 | }; 34 | } 35 | 36 | get _format() { 37 | return { 38 | date: { 39 | long: x => new Date(Date.parse(x)).toDateString(), 40 | short: x => { 41 | const date = new Date(Date.parse(x)); 42 | return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`; 43 | } 44 | }, 45 | listN: x => Number(x) || 5, 46 | num: x => { 47 | if (x >= (10 ** 6)) { 48 | return (x / (10 ** 6)).toFixed(2) + 'm'; 49 | } 50 | 51 | if (x >= (10 ** 3)) { 52 | return (x / (10 ** 3)).toFixed(2) + 'k'; 53 | } 54 | 55 | return x; 56 | }, 57 | underline: x => `\u001B[4m${x}\u001B[24m` 58 | }; 59 | } 60 | 61 | _getPopular(data) { 62 | const dlsTagsDict = data.reduce((acc, x) => { 63 | acc[x.dls] = x.tag; 64 | return acc; 65 | }, {}); 66 | 67 | const popularDls = Math.max(...Object.keys(dlsTagsDict)); 68 | 69 | return { 70 | popularDls, 71 | popularTag: dlsTagsDict[popularDls] 72 | }; 73 | } 74 | 75 | _releaseDls(x) { 76 | return x.assets.reduce((acc, asset) => acc + asset.download_count, 0); 77 | } 78 | 79 | _totalDls(data) { 80 | return data.reduce((acc, release) => acc + release.dls, 0); 81 | } 82 | 83 | _totalAssets(data) { 84 | return data.reduce((acc, release) => acc + release.assets, 0); 85 | } 86 | 87 | _badges(x) { 88 | const badges = []; 89 | 90 | if (x.isLatest) { 91 | badges.push('[Latest]'); 92 | } 93 | 94 | if (x.isPrerelease) { 95 | badges.push('[Pre-release]'); 96 | } 97 | 98 | return badges.join(' '); 99 | } 100 | 101 | _title(x) { 102 | return [ 103 | '*', 104 | this._format.underline(`Release ${x.tag}`), 105 | this._badges(x) 106 | ].join(' '); 107 | } 108 | 109 | _body(x) { 110 | const body = [ 111 | `Assets: ${this._format.num(x.assets)}`, 112 | `Downloads: ${this._format.num(x.dls)}`, 113 | `Date: ${x.date}`, 114 | `Author: @${x.author}` 115 | ]; 116 | 117 | return body.map(x => this._padding.concat(x, '\n')).join(''); 118 | } 119 | 120 | _formatData(data) { 121 | const result = []; 122 | 123 | data.forEach(release => { 124 | const {assets, created_at: date, prerelease: isPrerelease, tag_name: tag} = release; 125 | const {login: author} = release.author; 126 | 127 | result.push({ 128 | assets: assets.length, 129 | author, 130 | date: this._format.date.short(date), 131 | dls: this._releaseDls(release), 132 | isLatest: tag === this._latest.tag, 133 | isPrerelease, 134 | tag 135 | }); 136 | }); 137 | 138 | return result; 139 | } 140 | 141 | _getStats(data) { 142 | const [assets, dls, releases] = [this._totalAssets(data), this._totalDls(data), data.length]; 143 | const dlsPerRelease = Math.ceil(dls / releases); 144 | 145 | return Object.assign({ 146 | assets, 147 | dls, 148 | dlsPerRelease, 149 | releases 150 | }, this._getPopular(data)); 151 | } 152 | 153 | _displayRelease(x) { 154 | const release = [ 155 | this._title(x), 156 | this._body(x) 157 | ].join('\n'); 158 | 159 | log(release); 160 | } 161 | 162 | _displayStats(data) { 163 | const {tag: lt, date: ld} = this._latest; 164 | const {assets, dls, dlsPerRelease, releases, popularTag, popularDls} = this._getStats(data); 165 | 166 | const [a, d, dpr, r, pt, pd] = [ 167 | assets, 168 | dls, 169 | dlsPerRelease, 170 | releases, 171 | popularTag, 172 | popularDls 173 | ].map(this._format.num); 174 | 175 | const message = [ 176 | `Total: ${d} downloads, ${a} assets & ${r} releases.`, 177 | `On average: ${dpr} downloads per release.`, 178 | `Most popular: ${pt} with ${pd} downloads.`, 179 | `Latest: ${lt} on ${ld}.` 180 | ]; 181 | 182 | log(message.join('\n')); 183 | } 184 | 185 | _display(repo, data, n) { 186 | log(`\nLast ${n >= data.length ? data.length : n} releases of ${this._format.underline(repo)} repository:\n`); 187 | 188 | data.slice(0, n).reverse().forEach(x => { 189 | this._displayRelease(x); 190 | }); 191 | 192 | this._displayStats(data); 193 | } 194 | 195 | _get(path) { 196 | return new Promise((resolve, reject) => { 197 | const request = get(Object.assign(this._opts, {path}), response => { 198 | const body = []; 199 | 200 | response.on('data', x => body.push(x.toString('utf8'))); 201 | 202 | response.on('end', () => { 203 | const {statusCode: sc} = response; 204 | const data = JSON.parse(body.join('')); 205 | 206 | if (sc < 200 || sc > 299) { 207 | reject(new Error(`Request to get data failed with HTTP status code: ${sc} - ${data.message}`)); 208 | } 209 | 210 | resolve(data); 211 | }); 212 | }); 213 | 214 | request.on('error', err => reject(err)); 215 | 216 | request.end(); 217 | }); 218 | } 219 | 220 | async _getReleaseData(repo) { 221 | const total = await this._get(this._path.releases(repo)); 222 | 223 | if (Array.isArray(total) && total.length === 0) { 224 | throw new Error(`No available release data for the ${repo} repository`); 225 | } 226 | 227 | const latest = await this._get(this._path.latest(repo)); 228 | 229 | return [total, latest]; 230 | } 231 | 232 | async init(repo, n) { 233 | let [total, latest] = []; 234 | 235 | try { 236 | [total, latest] = await this._getReleaseData(repo); 237 | } catch (error) { 238 | return log(error); 239 | } 240 | 241 | this._latestRelease = Object.assign({}, latest); 242 | [total, n] = [this._formatData(total), this._format.listN(n)]; 243 | 244 | this._display(repo, total, n); 245 | } 246 | } 247 | 248 | module.exports = new Rels(); 249 | --------------------------------------------------------------------------------