├── config-example.js ├── .gitignore ├── package.json ├── README.md └── app.js /config-example.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Go get a personal API token from github.com 3 | token: 'abcpdq' 4 | }; 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | *.DS_Store 3 | *.npmignore 4 | node_modules 5 | config.js 6 | data.json 7 | stars.* 8 | starsq.* 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-stars-by-month", 3 | "version": "0.2.0", 4 | "description": "github stars by month in CSV format", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Apostrophe Technologies, Inc.", 10 | "license": "MIT", 11 | "dependencies": { 12 | "bluebird": "^3.5.0", 13 | "boring": "^0.1.0", 14 | "dayjs": "^1.8.25", 15 | "github": "^9.1.0", 16 | "lodash": "^4.17.13", 17 | "node-github-api": "0.0.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # github-stars-by-month 2 | 3 | First copy `config-example.js` to `config.js` and add your personal Github token 4 | , then: 5 | 6 | ``` 7 | node app apostrophecms/apostrophe 8 | ``` 9 | 10 | ## Options: 11 | 12 | ### frequency 13 | Use `--frequency=quarterly` for quarterly data. 14 | Use `--frequency=weekly` for weekly data (this uses a Sunday - Saturday week). 15 | 16 | ### reverse 17 | Use `--reverse` to see the data in reverse chronological order. 18 | 19 | ## TODO: 20 | 21 | - [ ] clean this up as something you can `npm install -g`. 22 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var GitHubApi = require("github"); 2 | var _ = require('lodash'); 3 | var config = require('./config.js'); 4 | var fs = require('fs'); 5 | var argv = require('boring')(); 6 | var dayjs = require('dayjs'); 7 | var weekOfYear = require('dayjs/plugin/weekOfYear'); 8 | var weekYear = require('dayjs/plugin/weekYear') 9 | dayjs.extend(weekOfYear); 10 | dayjs.extend(weekYear); 11 | 12 | var starsGrouped = {}; 13 | 14 | var github = new GitHubApi({ 15 | // optional 16 | protocol: "https", 17 | headers: { 18 | "user-agent": "stars-by-month" // GitHub is happy with a unique user agent 19 | }, 20 | Promise: require('bluebird'), 21 | timeout: 5000 22 | }); 23 | github.authenticate({ 24 | type: 'token', 25 | token: config.token 26 | }); 27 | 28 | // TODO: optional authentication here depending on desired endpoints. See below in README. 29 | 30 | github.activity.getStargazersForRepo({ 31 | owner: 'apostrophecms', 32 | repo: 'apostrophe', 33 | headers: { 34 | Accept: 'application/vnd.github.v3.star+json', 35 | }, 36 | }, next); 37 | 38 | function next(err, result) { 39 | if (err) { 40 | console.error(err); 41 | return; 42 | } 43 | 44 | if (argv.frequency === 'weekly') { 45 | result.data.forEach(groupByWeek); 46 | } else { 47 | result.data.forEach(groupByMonth); 48 | } 49 | 50 | if (github.hasNextPage(result)) { 51 | return github.getNextPage(result, { 52 | Accept: 'application/vnd.github.v3.star+json', 53 | }, next); 54 | } 55 | done(); 56 | } 57 | 58 | function groupByMonth (datum) { 59 | var matches = datum.starred_at.match(/^\d\d\d\d\-\d\d/); 60 | if (!matches) { 61 | return; 62 | } 63 | if (!starsGrouped[matches[0]]) { 64 | starsGrouped[matches[0]] = 0; 65 | } 66 | starsGrouped[matches[0]]++; 67 | } 68 | 69 | function groupByWeek (datum) { 70 | var matches = datum.starred_at.match(/^\d\d\d\d\-\d\d\-\d\d/); 71 | if (!matches) { 72 | return; 73 | } 74 | 75 | const year = dayjs(matches[0]).weekYear(); 76 | let week = dayjs(matches[0]).week(); 77 | week = week.toString().length === 2 ? week : '0' + week; 78 | console.log(`${year}-${week}`); 79 | if (!starsGrouped[`${year}-${week}`]) { 80 | starsGrouped[`${year}-${week}`] = 0; 81 | } 82 | starsGrouped[`${year}-${week}`]++; 83 | } 84 | 85 | function done() { 86 | fs.writeFileSync('./data.json', JSON.stringify(starsGrouped)); 87 | if (argv.frequency === 'quarterly') { 88 | quarterlyReport(); 89 | } else if (argv.frequency === 'weekly') { 90 | weeklyReport(); 91 | } else { 92 | monthlyReport(); 93 | } 94 | } 95 | 96 | function monthlyReport() { 97 | var keys = _.keys(starsGrouped); 98 | var first = _.first(keys); 99 | var last = _.last(keys); 100 | var stamp = first; 101 | var matches; 102 | var lines = []; 103 | 104 | while (stamp <= last) { 105 | lines.push(stamp + ',' + (starsGrouped[stamp] || 0)) 106 | matches = stamp.match(/^(\d\d\d\d)\-(\d\d)$/); 107 | var year, month; 108 | if (matches) { 109 | year = parseInt(matches[1]); 110 | month = parseInt(matches[2]); 111 | month++; 112 | if (month > 12) { 113 | month = 1; 114 | year++; 115 | } 116 | if (month < 10) { 117 | month = '0' + month.toString(); 118 | } 119 | stamp = year + '-' + month; 120 | } 121 | } 122 | 123 | if (argv.reverse) { 124 | lines.reverse(); 125 | } 126 | 127 | lines.forEach(line => { 128 | console.log(line); 129 | }); 130 | } 131 | 132 | function quarterlyReport() { 133 | var starsByQuarter = {}; 134 | _.each(starsGrouped, function(stars, quarter) { 135 | matches = quarter.match(/^(\d\d\d\d)\-(\d\d)$/); 136 | var year, month, quarter; 137 | if (matches) { 138 | year = parseInt(matches[1]); 139 | month = parseInt(matches[2]); 140 | quarter = (((month - 1) - ((month - 1) % 3)) / 3) + 1; 141 | quarter = '0' + quarter; 142 | var quarter = year + '-' + quarter; 143 | starsByQuarter[quarter] = starsByQuarter[quarter] || 0; 144 | starsByQuarter[quarter] += stars; 145 | } 146 | }); 147 | var keys = _.keys(starsByQuarter); 148 | var first = _.first(keys); 149 | var last = _.last(keys); 150 | var stamp = first; 151 | var matches; 152 | 153 | var lines = []; 154 | 155 | while (stamp <= last) { 156 | lines.push(stamp + ',' + (starsByQuarter[stamp] || 0)); 157 | matches = stamp.match(/^(\d\d\d\d)\-(\d\d)$/); 158 | var year, quarter; 159 | if (matches) { 160 | year = parseInt(matches[1]); 161 | quarter = parseInt(matches[2]); 162 | quarter++; 163 | if (quarter > 4) { 164 | quarter = 1; 165 | year++; 166 | } 167 | quarter = '0' + quarter.toString(); 168 | stamp = year + '-' + quarter; 169 | } 170 | } 171 | 172 | if (argv.reverse) { 173 | lines.reverse(); 174 | } 175 | 176 | lines.forEach(line => { 177 | console.log(line); 178 | }); 179 | } 180 | 181 | function weeklyReport() { 182 | var keys = _.keys(starsGrouped); 183 | var first = _.first(keys); 184 | var last = _.last(keys); 185 | var stamp = first; 186 | var matches; 187 | var lines = []; 188 | 189 | while (stamp <= last) { 190 | lines.push(stamp + ',' + (starsGrouped[stamp] || 0)) 191 | matches = stamp.match(/^(\d\d\d\d)\-(\d\d)$/); 192 | var year, week; 193 | if (matches) { 194 | year = parseInt(matches[1]); 195 | week = parseInt(matches[2]); 196 | week++; 197 | if (week > 52) { 198 | week = 1; 199 | year++; 200 | } 201 | if (week < 10) { 202 | week = '0' + week.toString(); 203 | } 204 | stamp = year + '-' + week; 205 | } 206 | } 207 | 208 | if (argv.reverse) { 209 | lines.reverse(); 210 | } 211 | 212 | lines.forEach(line => { 213 | console.log(line); 214 | }); 215 | } --------------------------------------------------------------------------------