├── .gitignore ├── README.md ├── index.js ├── package.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | sharelove 3 |

4 | 5 | Give thanks (in the form of a [GitHub ★ ](https://help.github.com/articles/about-stars/)) to your fellow JS package maintainers! 6 | 7 |
8 |
9 | 10 | ## Usage 11 | 12 | ```sh 13 | $ npx sharelove 14 | ``` 15 | 16 | This will find all of your NPM dependencies, find their github.com repository, and star their GitHub repositories. This was inspired by [`symfony/thanks`](https://github.com/symfony/thanks). 17 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const got = require('got') 4 | const { join } = require('path') 5 | const colors = require('colors') 6 | const emoji = require('node-emoji') 7 | const { dependencies, devDependencies } = require(join(process.cwd(), 'package.json')) 8 | 9 | const aliases = {} 10 | const notStarred = {} 11 | const cannotStar = [] 12 | const allDependencies = Object.keys({ ...dependencies, ...devDependencies }) 13 | 14 | if (!process.env.GITHUB_API_TOKEN) { 15 | console.log(colors.red(`${emoji.get('warning')} We use GITHUB_API_TOKEN environment variable to call the Github API.`)) 16 | console.log(colors.red(`Please run ${colors.bold('export GITHUB_API_TOKEN="YOUR_TOKEN"')} and try again.`)) 17 | process.exit() 18 | } 19 | 20 | async function callGithubApi (query) { 21 | const { body } = await got.post('https://api.github.com/graphql', { 22 | body: { query }, 23 | headers: { authorization: `Bearer ${process.env.GITHUB_API_TOKEN}` }, 24 | json: true, 25 | }) 26 | 27 | return body.data 28 | } 29 | 30 | async function fetchPackagesInformation () { 31 | const { body } = await got.post('https://api.npms.io/v2/package/mget', { 32 | body: allDependencies, 33 | json: true, 34 | }) 35 | 36 | return Object.entries(body).reduce((pkgs, [pkgName, pkgInfo]) => { 37 | try { 38 | const url = pkgInfo.collected.metadata.repository.url.split('/') 39 | const owner = url[3] 40 | const repository = url[4].substring(0, url[4].lastIndexOf('.')) 41 | 42 | pkgs.push({ name: pkgName, owner, repository }) 43 | } catch (e) { 44 | cannotStar.push(pkgName) 45 | } 46 | 47 | return pkgs 48 | }, []) 49 | } 50 | 51 | async function thanksDependencies () { 52 | const pkgs = await fetchPackagesInformation() 53 | let query = '' 54 | 55 | pkgs.forEach(({ name, owner, repository }, index) => { 56 | query += `_${index}: repository(owner:"${owner}",name:"${repository}"){id,viewerHasStarred}\n` 57 | aliases[`_${index}`] = { name, owner, repository } 58 | }) 59 | 60 | const repos = await callGithubApi(`query{${query}}`) 61 | query = '' 62 | 63 | Object.entries(repos).forEach(([alias, repo], index) => { 64 | if (!repo) { 65 | cannotStar.push(aliases[alias].name) 66 | return 67 | } 68 | 69 | if (!repo.viewerHasStarred) { 70 | query += `_${index}: addStar(input:{clientMutationId:"${alias}",starrableId:"${repo.id}"}){clientMutationId}\n` 71 | notStarred[alias] = repo 72 | } 73 | }) 74 | 75 | if (Object.keys(notStarred).length <= 0) { 76 | console.log(`You already starred all your GitHub dependencies. ${emoji.get('heart')}\n`) 77 | } else { 78 | await callGithubApi(`mutation{${query}}`) 79 | 80 | console.log(`Stars sent to :`) 81 | 82 | Object.entries(notStarred).forEach(([alias, repo]) => { 83 | console.log(` - ${emoji.get('star')} ${colors.blue(aliases[alias].name)}`) 84 | }) 85 | } 86 | 87 | cannotStar.forEach((name) => { 88 | console.log(` - Cannot ${emoji.get('star')} ${colors.blue(name)} ${emoji.get('cry')}`) 89 | }) 90 | 91 | console.log(`\nThanks to you! ${emoji.get('heart')}`) 92 | 93 | process.exit() 94 | } 95 | 96 | thanksDependencies() 97 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sharelove", 3 | "version": "1.0.4", 4 | "description": "Give thanks (in the form of a GitHub ★) to your fellow JS package maintainers.", 5 | "keywords": [ 6 | "thanks", 7 | "stars", 8 | "github", 9 | "dependencies" 10 | ], 11 | "author": "Romain Lanz ", 12 | "license": "MIT", 13 | "main": "index.js", 14 | "bin": "index.js", 15 | "dependencies": { 16 | "colors": "^1.1.2", 17 | "got": "^8.2.0", 18 | "node-emoji": "^1.8.1" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/Slynova-Org/sharelove" 23 | }, 24 | "bugs": { 25 | "url": "https://github.com/Slynova-Org/sharelove/issues" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@sindresorhus/is@^0.7.0": 6 | version "0.7.0" 7 | resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" 8 | 9 | cacheable-request@^2.1.1: 10 | version "2.1.4" 11 | resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d" 12 | dependencies: 13 | clone-response "1.0.2" 14 | get-stream "3.0.0" 15 | http-cache-semantics "3.8.1" 16 | keyv "3.0.0" 17 | lowercase-keys "1.0.0" 18 | normalize-url "2.0.1" 19 | responselike "1.0.2" 20 | 21 | clone-response@1.0.2: 22 | version "1.0.2" 23 | resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" 24 | dependencies: 25 | mimic-response "^1.0.0" 26 | 27 | colors@^1.1.2: 28 | version "1.1.2" 29 | resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" 30 | 31 | core-util-is@~1.0.0: 32 | version "1.0.2" 33 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 34 | 35 | decode-uri-component@^0.2.0: 36 | version "0.2.0" 37 | resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" 38 | 39 | decompress-response@^3.3.0: 40 | version "3.3.0" 41 | resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" 42 | dependencies: 43 | mimic-response "^1.0.0" 44 | 45 | duplexer3@^0.1.4: 46 | version "0.1.4" 47 | resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" 48 | 49 | from2@^2.1.1: 50 | version "2.3.0" 51 | resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" 52 | dependencies: 53 | inherits "^2.0.1" 54 | readable-stream "^2.0.0" 55 | 56 | get-stream@3.0.0, get-stream@^3.0.0: 57 | version "3.0.0" 58 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" 59 | 60 | got@^8.2.0: 61 | version "8.2.0" 62 | resolved "https://registry.yarnpkg.com/got/-/got-8.2.0.tgz#0d11a071d05046348a2f5c0a5fa047fb687fdfc6" 63 | dependencies: 64 | "@sindresorhus/is" "^0.7.0" 65 | cacheable-request "^2.1.1" 66 | decompress-response "^3.3.0" 67 | duplexer3 "^0.1.4" 68 | get-stream "^3.0.0" 69 | into-stream "^3.1.0" 70 | is-retry-allowed "^1.1.0" 71 | isurl "^1.0.0-alpha5" 72 | lowercase-keys "^1.0.0" 73 | mimic-response "^1.0.0" 74 | p-cancelable "^0.3.0" 75 | p-timeout "^2.0.1" 76 | pify "^3.0.0" 77 | safe-buffer "^5.1.1" 78 | timed-out "^4.0.1" 79 | url-parse-lax "^3.0.0" 80 | url-to-options "^1.0.1" 81 | 82 | has-symbol-support-x@^1.4.1: 83 | version "1.4.1" 84 | resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.1.tgz#66ec2e377e0c7d7ccedb07a3a84d77510ff1bc4c" 85 | 86 | has-to-string-tag-x@^1.2.0: 87 | version "1.4.1" 88 | resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" 89 | dependencies: 90 | has-symbol-support-x "^1.4.1" 91 | 92 | http-cache-semantics@3.8.1: 93 | version "3.8.1" 94 | resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" 95 | 96 | inherits@^2.0.1, inherits@~2.0.3: 97 | version "2.0.3" 98 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 99 | 100 | into-stream@^3.1.0: 101 | version "3.1.0" 102 | resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" 103 | dependencies: 104 | from2 "^2.1.1" 105 | p-is-promise "^1.1.0" 106 | 107 | is-object@^1.0.1: 108 | version "1.0.1" 109 | resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" 110 | 111 | is-plain-obj@^1.0.0: 112 | version "1.1.0" 113 | resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" 114 | 115 | is-retry-allowed@^1.1.0: 116 | version "1.1.0" 117 | resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" 118 | 119 | isarray@~1.0.0: 120 | version "1.0.0" 121 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 122 | 123 | isurl@^1.0.0-alpha5: 124 | version "1.0.0" 125 | resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" 126 | dependencies: 127 | has-to-string-tag-x "^1.2.0" 128 | is-object "^1.0.1" 129 | 130 | json-buffer@3.0.0: 131 | version "3.0.0" 132 | resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" 133 | 134 | keyv@3.0.0: 135 | version "3.0.0" 136 | resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373" 137 | dependencies: 138 | json-buffer "3.0.0" 139 | 140 | lodash.toarray@^4.4.0: 141 | version "4.4.0" 142 | resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" 143 | 144 | lowercase-keys@1.0.0, lowercase-keys@^1.0.0: 145 | version "1.0.0" 146 | resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" 147 | 148 | mimic-response@^1.0.0: 149 | version "1.0.0" 150 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" 151 | 152 | node-emoji@^1.8.1: 153 | version "1.8.1" 154 | resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.8.1.tgz#6eec6bfb07421e2148c75c6bba72421f8530a826" 155 | dependencies: 156 | lodash.toarray "^4.4.0" 157 | 158 | normalize-url@2.0.1: 159 | version "2.0.1" 160 | resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" 161 | dependencies: 162 | prepend-http "^2.0.0" 163 | query-string "^5.0.1" 164 | sort-keys "^2.0.0" 165 | 166 | object-assign@^4.1.0: 167 | version "4.1.1" 168 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 169 | 170 | p-cancelable@^0.3.0: 171 | version "0.3.0" 172 | resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" 173 | 174 | p-finally@^1.0.0: 175 | version "1.0.0" 176 | resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" 177 | 178 | p-is-promise@^1.1.0: 179 | version "1.1.0" 180 | resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" 181 | 182 | p-timeout@^2.0.1: 183 | version "2.0.1" 184 | resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" 185 | dependencies: 186 | p-finally "^1.0.0" 187 | 188 | pify@^3.0.0: 189 | version "3.0.0" 190 | resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" 191 | 192 | prepend-http@^2.0.0: 193 | version "2.0.0" 194 | resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" 195 | 196 | process-nextick-args@~2.0.0: 197 | version "2.0.0" 198 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" 199 | 200 | query-string@^5.0.1: 201 | version "5.1.0" 202 | resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.0.tgz#9583b15fd1307f899e973ed418886426a9976469" 203 | dependencies: 204 | decode-uri-component "^0.2.0" 205 | object-assign "^4.1.0" 206 | strict-uri-encode "^1.0.0" 207 | 208 | readable-stream@^2.0.0: 209 | version "2.3.4" 210 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.4.tgz#c946c3f47fa7d8eabc0b6150f4a12f69a4574071" 211 | dependencies: 212 | core-util-is "~1.0.0" 213 | inherits "~2.0.3" 214 | isarray "~1.0.0" 215 | process-nextick-args "~2.0.0" 216 | safe-buffer "~5.1.1" 217 | string_decoder "~1.0.3" 218 | util-deprecate "~1.0.1" 219 | 220 | responselike@1.0.2: 221 | version "1.0.2" 222 | resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" 223 | dependencies: 224 | lowercase-keys "^1.0.0" 225 | 226 | safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: 227 | version "5.1.1" 228 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" 229 | 230 | sort-keys@^2.0.0: 231 | version "2.0.0" 232 | resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" 233 | dependencies: 234 | is-plain-obj "^1.0.0" 235 | 236 | strict-uri-encode@^1.0.0: 237 | version "1.1.0" 238 | resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" 239 | 240 | string_decoder@~1.0.3: 241 | version "1.0.3" 242 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" 243 | dependencies: 244 | safe-buffer "~5.1.0" 245 | 246 | timed-out@^4.0.1: 247 | version "4.0.1" 248 | resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" 249 | 250 | url-parse-lax@^3.0.0: 251 | version "3.0.0" 252 | resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" 253 | dependencies: 254 | prepend-http "^2.0.0" 255 | 256 | url-to-options@^1.0.1: 257 | version "1.0.1" 258 | resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" 259 | 260 | util-deprecate@~1.0.1: 261 | version "1.0.2" 262 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 263 | --------------------------------------------------------------------------------