├── .npmignore ├── .travis.yml ├── README.md ├── bin └── cmd.js ├── img ├── example.gif ├── icon.png ├── icon.svg ├── vertical.png └── vertical.svg ├── index.js └── package.json /.npmignore: -------------------------------------------------------------------------------- 1 | .travis.yml 2 | img/ 3 | src/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - lts/* 4 | install: 5 | - npm install 6 | - gem install awesome_bot 7 | script: 8 | - npm test 9 | - awesome_bot index.js --allow-dupe --allow-redirect --skip-save-results --request-delay 1 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Thanks 3 |
4 |

5 | 6 |

🙌 Give thanks to the open source maintainers you depend on! ✨

7 | 8 |

9 | travis 10 | npm version 11 | npm downloads 12 | Standard - JavaScript Style Guide 13 |

14 |
15 | 16 | ![example gif](img/example.gif) 17 | 18 | > "Put your money where your love is." 19 | > – The Grateful Dead 20 | 21 | Open source maintainers do the work that makes our awesome apps, websites, and projects possible! Many authors devote countless hours to open source. Let's help out authors and make the software we rely on healthier at the same time! 22 | 23 | [Vote for us on Product Hunt](https://www.producthunt.com/posts/thanks) ❤️ 24 | 25 | ## Usage 26 | 27 | It's easy! 28 | 29 | 1. Run `npx thanks` in your project 30 | 2. See which of your dependencies are seeking donations! 💸 31 | 32 | ## Install 33 | 34 | Run it instantly (without installing!) using: 35 | 36 | ```js 37 | npx thanks 38 | ``` 39 | 40 | Or, install it, then run it: 41 | 42 | ```js 43 | npm install -g thanks 44 | thanks 45 | ``` 46 | 47 | ## 🌟 Open source authors, add yourself to the list 48 | 49 | If you're an open source author who accepts donations, add yourself to the `thanks` CLI by [modifying this file](https://github.com/feross/thanks/blob/master/index.js), and sending a pull request! 50 | 51 | We're also considering [supporting a new `package.json` field](https://github.com/feross/thanks/issues/2). Please share your thoughts! 52 | 53 | ## License 54 | 55 | MIT. Copyright (c) [Feross Aboukhadijeh](https://feross.org). 56 | -------------------------------------------------------------------------------- /bin/cmd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const chalk = require('chalk') 4 | const got = require('got') // TODO: use simple-get when it supports promises 5 | const minimist = require('minimist') 6 | const open = require('open') 7 | const ora = require('ora') 8 | const pify = require('pify') 9 | const pkgDir = require('pkg-dir') 10 | const pkgUp = require('pkg-up') 11 | const PromptConfirm = require('prompt-confirm') 12 | const readPackageTree = require('read-package-tree') 13 | const registryAuthToken = require('registry-auth-token') 14 | const RegistryClient = require('npm-registry-client') // TODO: use npm-registry-fetch when done 15 | const registryUrl = require('registry-url') 16 | const stripAnsi = require('strip-ansi') 17 | const termSize = require('term-size') 18 | const textTable = require('text-table') 19 | const { readFile } = require('fs') 20 | const { stripIndent } = require('common-tags') 21 | 22 | const thanks = require('../') 23 | 24 | const readFileAsync = pify(readFile) 25 | const readPackageTreeAsync = pify(readPackageTree) 26 | 27 | const DOWNLOADS_URL = 'https://api.npmjs.org/downloads/point/last-month/' 28 | const DOWNLOADS_URL_LIMIT = 128 29 | const RE_URL_PREFIX = /https?:\/\/(www\.)?/ 30 | const RE_TRAILING_SLASH = /\/$/ 31 | const HEARTS_SPINNER = { 32 | interval: 100, 33 | frames: [ 34 | '💛 ', 35 | '💙 ', 36 | '💜 ', 37 | '💚 ' 38 | ] 39 | } 40 | 41 | let spinner 42 | 43 | init() 44 | .catch(function (err) { 45 | const message = `Error: ${err.message}\n` 46 | 47 | if (spinner) spinner.fail(message) 48 | else console.error(message) 49 | 50 | console.error( 51 | chalk`{cyan Found a bug?} Open an issue at {magenta https://github.com/feross/thanks}\n` 52 | ) 53 | console.error(err.stack) 54 | process.exitCode = 1 55 | }) 56 | 57 | async function init () { 58 | const argv = minimist(process.argv.slice(2), { 59 | boolean: [ 60 | 'open' 61 | ], 62 | alias: { 63 | h: 'help', 64 | v: 'version' 65 | }, 66 | default: { 67 | open: true 68 | } 69 | }) 70 | const cwd = argv._[0] || process.cwd() 71 | 72 | if (argv.help) { 73 | return runHelp() 74 | } 75 | if (argv.version) { 76 | return runVersion() 77 | } 78 | return runThanks(cwd, argv.open) 79 | } 80 | 81 | function runHelp () { 82 | const message = stripIndent` 83 | thanks - 🙌 Give thanks to the open source maintainers you depend on! ✨ 84 | 85 | Usage: 86 | thanks [CWD] 87 | 88 | If CWD is omitted, then the current working directory is used. The "nearest" 89 | package.json / node_modules folder will be used. 90 | 91 | Flags: 92 | -v, --version Show current version 93 | -h, --help Show usage information 94 | 95 | ` 96 | console.log(message) 97 | } 98 | 99 | function runVersion () { 100 | console.log(require('../package.json').version) 101 | } 102 | 103 | function englishJoin (...arr) { 104 | arr = arr.filter(Boolean) 105 | switch (arr.length) { 106 | case 3: return `${arr[0]}, ${arr[1]}, and ${arr[2]}` 107 | case 2: return `${arr[0]} and ${arr[1]}` 108 | case 1: return `${arr[0]}` 109 | } 110 | } 111 | 112 | async function runThanks (cwd, promptToOpen) { 113 | spinner = ora({ 114 | spinner: HEARTS_SPINNER, 115 | text: chalk`Getting ready to {cyan give thanks} to {magenta maintainers}...` 116 | }).start() 117 | 118 | const client = createRegistryClient() 119 | 120 | spinner.text = chalk`Reading {cyan direct dependencies} from metadata in {magenta package.json}...` 121 | const directPkgNames = await readDirectPkgNames() 122 | 123 | spinner.text = chalk`Reading {cyan dependencies} from package tree in {magenta node_modules}...` 124 | const rootPath = await pkgDir(cwd) 125 | const packageTree = await readPackageTreeAsync(rootPath) 126 | const pkgNames = packageTree.children 127 | .map(node => node.package.name) 128 | // Filter out folders without a package.json in node_modules 129 | // See: https://github.com/feross/thanks/issues/72 130 | .filter(Boolean) 131 | 132 | if (pkgNames.length === 0) { 133 | spinner.fail(chalk`{red No packages} found in the {magenta node_modules} folder. Try running {cyan npm install} first, silly! 😆`) 134 | return 135 | } 136 | 137 | // Get latest registry data on each local package, since the local data does 138 | // not include the list of maintainers 139 | spinner.text = chalk`Fetching package {cyan maintainers} from {red npm}...` 140 | const pkgs = await fetchPkgs(client, pkgNames) 141 | 142 | spinner.text = chalk`Fetching package {cyan download counts} from {red npm}...` 143 | const pkgDownloads = await bulkFetchPkgDownloads(pkgNames) 144 | 145 | // Author name -> list of packages (sorted by direct dependencies, then download count) 146 | const authorsPkgNames = computeAuthorsPkgNames(pkgs, pkgDownloads, directPkgNames) 147 | 148 | // Org name -> list of packages (sorted by direct dependencies, then download count) 149 | const orgsPkgNames = computeOrgPkgNames(pkgs, pkgDownloads, directPkgNames) 150 | 151 | // Array of author names who are seeking donations (sorted by download count) 152 | const authorsSeeking = Object.keys(authorsPkgNames) 153 | .filter(author => thanks.authors[author] != null) 154 | .sort((author1, author2) => authorsPkgNames[author2].length - authorsPkgNames[author1].length) 155 | 156 | // Array of package names that are seeking donations (sorted by download count) 157 | const pkgNamesSeeking = pkgNames 158 | .filter(pkgName => thanks.packages[pkgName] != null) 159 | .sort((pkg1, pkg2) => pkgDownloads[pkg2] - pkgDownloads[pkg1]) 160 | 161 | // Array of organization names who are seeking donations (sorted by download count) 162 | const orgsSeeking = Object.keys(orgsPkgNames) 163 | .filter(org => thanks.organizations[org] != null) 164 | .sort((org1, org2) => orgsPkgNames[org2].length - orgsPkgNames[org1].length) 165 | 166 | const donateLinks = [].concat( 167 | authorsSeeking.map(author => thanks.authors[author]), 168 | pkgNamesSeeking.map(pkgName => thanks.packages[pkgName]), 169 | orgsSeeking.map(org => thanks.organizations[org]) 170 | ) 171 | 172 | const authorStr = authorsSeeking.length && chalk.cyan(`${authorsSeeking.length} authors`) 173 | const pkgNamesStr = pkgNamesSeeking.length && chalk.cyan(`${pkgNamesSeeking.length} teams`) 174 | const orgNamesStr = orgsSeeking.length && chalk.cyan(`${orgsSeeking.length} organizations`) 175 | 176 | const listCounts = englishJoin(authorStr, pkgNamesStr, orgNamesStr) 177 | 178 | if (listCounts) { 179 | spinner.succeed( 180 | chalk`You depend on ${listCounts} who are {magenta seeking donations!} ✨\n` 181 | ) 182 | } else { 183 | spinner.succeed( 184 | chalk`You depend on {cyan no authors} who are seeking donations! 😌` 185 | ) 186 | } 187 | 188 | if (authorsSeeking.length > 0 || pkgNamesSeeking.length > 0 || orgsSeeking.length > 0) { 189 | printTable(authorsSeeking, pkgNamesSeeking, orgsSeeking, authorsPkgNames, orgsPkgNames, directPkgNames) 190 | } 191 | 192 | printInstructions() 193 | 194 | if (donateLinks.length && promptToOpen) { 195 | const prompt = new PromptConfirm( 196 | chalk`Want to open these {cyan donate pages} in your {magenta web browser}? 🦄` 197 | ) 198 | const doOpen = await prompt.run() 199 | if (doOpen) openDonateLinks(donateLinks) 200 | } 201 | } 202 | 203 | function createRegistryClient () { 204 | const opts = { 205 | log: { 206 | error () {}, 207 | http () {}, 208 | info () {}, 209 | silly () {}, 210 | verbose () {}, 211 | warn () {} 212 | } 213 | } 214 | const client = new RegistryClient(opts) 215 | client.getAsync = pify(client.get.bind(client)) 216 | return client 217 | } 218 | 219 | function isScopedPkg (pkgName) { 220 | return pkgName.includes('/') 221 | } 222 | 223 | function getScopedPkgOrg (pkgName) { 224 | return pkgName.match(/@([^/]+)/)[1] || null 225 | } 226 | 227 | async function fetchPkgs (client, pkgNames) { 228 | const pkgs = await Promise.all(pkgNames.map(fetchPkg)) 229 | 230 | // Filter out `null`s which come from private packages or GitHub dependencies 231 | // which don't exist on npm (so don't have package metadata) 232 | return pkgs.filter(Boolean) 233 | 234 | async function fetchPkg (pkgName) { 235 | // Note: The registry does not support fetching versions for scoped packages 236 | const url = isScopedPkg(pkgName) 237 | ? `${registryUrl()}${pkgName.replace(/\//g, '%2F')}` 238 | : `${registryUrl()}${pkgName}/latest` 239 | 240 | const opts = { 241 | timeout: 30 * 1000, 242 | staleOk: true, 243 | auth: registryAuthToken() 244 | } 245 | 246 | let pkg = null 247 | try { 248 | pkg = await client.getAsync(url, opts) 249 | } catch (err) { 250 | // Private packages or GitHub dependecies that don't exist on npm will return 251 | // 404 errors, so just skip those packages 252 | } 253 | return pkg 254 | } 255 | } 256 | 257 | function printTable (authorsSeeking, pkgNamesSeeking, orgsSeeking, authorsPkgNames, orgsPkgNames, directPkgNames) { 258 | // Highlight direct dependencies in a different color 259 | function maybeHighlightPkgName (pkgName) { 260 | return directPkgNames.includes(pkgName) 261 | ? chalk.green.bold(pkgName) 262 | : pkgName 263 | } 264 | 265 | const authorRows = authorsSeeking 266 | .map(author => { 267 | const authorPkgNames = authorsPkgNames[author].map(maybeHighlightPkgName) 268 | const donateLink = prettyUrl(thanks.authors[author]) 269 | return [ 270 | author, 271 | chalk.cyan(donateLink), 272 | listWithMaxLen(authorPkgNames, termSize().columns - 50) 273 | ] 274 | }) 275 | 276 | const packageRows = pkgNamesSeeking 277 | .map(pkgName => { 278 | const donateLink = prettyUrl(thanks.packages[pkgName]) 279 | return [ 280 | `${pkgName} (team)`, 281 | chalk.cyan(donateLink), 282 | maybeHighlightPkgName(pkgName) 283 | ] 284 | }) 285 | 286 | const orgRows = orgsSeeking 287 | .map(org => { 288 | const orgPkgNames = orgsPkgNames[org].map(maybeHighlightPkgName) 289 | const donateLink = prettyUrl(thanks.organizations[org]) 290 | return [ 291 | `${org} (organization)`, 292 | chalk.cyan(donateLink), 293 | listWithMaxLen(orgPkgNames, termSize().columns - 50) 294 | ] 295 | }) 296 | 297 | const rows = [[ 298 | chalk.underline('Author'), 299 | chalk.underline('Where to Donate'), 300 | chalk.underline('Dependencies') 301 | ]].concat( 302 | authorRows, 303 | orgRows, 304 | packageRows 305 | ) 306 | 307 | const opts = { 308 | stringLength: str => stripAnsi(str).length 309 | } 310 | const table = textTable(rows, opts) 311 | console.log(table + '\n') 312 | } 313 | 314 | function prettyUrl (url) { 315 | return url 316 | .replace(RE_URL_PREFIX, '') 317 | .replace(RE_TRAILING_SLASH, '') 318 | } 319 | 320 | async function bulkFetchPkgDownloads (pkgNames) { 321 | // A few notes: 322 | // - bulk queries do not support scoped packages 323 | // - bulk queries are limited to at most 128 packages at a time 324 | const pkgDownloads = {} 325 | 326 | const normalPkgNames = pkgNames.filter(pkgName => !isScopedPkg(pkgName)) 327 | const scopedPkgNames = pkgNames.filter(isScopedPkg) 328 | 329 | for (let start = 0; start < normalPkgNames.length; start += DOWNLOADS_URL_LIMIT) { 330 | const pkgNamesSubset = normalPkgNames.slice(start, start + DOWNLOADS_URL_LIMIT) 331 | const url = DOWNLOADS_URL + pkgNamesSubset.join(',') 332 | let res 333 | try { 334 | res = await got(url).json() 335 | } catch (err) { 336 | // If a single package is requested and does not exists, it will return a 404 337 | // error. Ignore the error. 338 | continue 339 | } 340 | Object.keys(res).forEach(pkgName => { 341 | const stats = res[pkgName] 342 | // If multiple packages are requested and some of them do not exist, those keys 343 | // will have a value of null. Skip those packages. 344 | if (stats) pkgDownloads[pkgName] = stats.downloads 345 | }) 346 | } 347 | 348 | // Scoped packages must be requested individually since they're not supported in 349 | // bulk queries. 350 | await Promise.all(scopedPkgNames.map(async scopedPkgName => { 351 | const url = DOWNLOADS_URL + scopedPkgName 352 | let res 353 | try { 354 | res = await got(url).json() 355 | pkgDownloads[scopedPkgName] = res.downloads 356 | } catch (err) { 357 | // If a single package is requested and does not exists, it will return a 404 358 | // error. Ignore the error. 359 | } 360 | })) 361 | 362 | return pkgDownloads 363 | } 364 | 365 | function computeAuthorsPkgNames (pkgs, pkgDownloads, directPkgNames) { 366 | // author name -> array of package names 367 | const authorPkgNames = {} 368 | 369 | pkgs.forEach(pkg => { 370 | if (!pkg.maintainers) { 371 | // Ignore packages that are missing a "maintainers" field (e.g. 372 | // http://registry.npmjs.com/vargs/latest). This appears to happen on very old 373 | // packages. My guess is that the "maintainers" field only started getting 374 | // added to release metadata recently. 375 | return 376 | } 377 | pkg.maintainers 378 | .map(maintainer => maintainer.name) 379 | .forEach(author => { 380 | if (authorPkgNames[author] == null) authorPkgNames[author] = [] 381 | authorPkgNames[author].push(pkg.name) 382 | }) 383 | }) 384 | 385 | // Sort each author's package list by direct dependencies, then download count 386 | // dependencies first in the list 387 | Object.keys(authorPkgNames).forEach(author => { 388 | const authorDirectPkgNames = authorPkgNames[author] 389 | .filter(pkgName => directPkgNames.includes(pkgName)) 390 | 391 | const pkgNames = authorPkgNames[author] 392 | .filter(pkgName => !authorDirectPkgNames.includes(pkgName)) 393 | .sort((pkg1, pkg2) => pkgDownloads[pkg2] - pkgDownloads[pkg1]) 394 | 395 | pkgNames.unshift(...authorDirectPkgNames) 396 | 397 | authorPkgNames[author] = pkgNames 398 | }) 399 | 400 | return authorPkgNames 401 | } 402 | 403 | function computeOrgPkgNames (pkgs, pkgDownloads, directPkgNames) { 404 | // org -> array of package names 405 | const orgPkgNames = {} 406 | 407 | pkgs.forEach(pkg => { 408 | if (isScopedPkg(pkg.name)) { 409 | const org = getScopedPkgOrg(pkg.name) 410 | if (!orgPkgNames[org]) { 411 | orgPkgNames[org] = [] 412 | } 413 | orgPkgNames[org].push(pkg.name) 414 | } 415 | }) 416 | 417 | // Sort each org's package list by direct dependencies, then download count 418 | // dependencies first in the list 419 | Object.keys(orgPkgNames).forEach(org => { 420 | const orgDirectPkgNames = orgPkgNames[org] 421 | .filter(pkgName => directPkgNames.includes(pkgName)) 422 | 423 | const pkgNames = orgPkgNames[org] 424 | .filter(pkgName => !orgDirectPkgNames.includes(pkgName)) 425 | .sort((pkg1, pkg2) => pkgDownloads[pkg2] - pkgDownloads[pkg1]) 426 | 427 | pkgNames.unshift(...orgDirectPkgNames) 428 | 429 | orgPkgNames[org] = pkgNames 430 | }) 431 | 432 | return orgPkgNames 433 | } 434 | 435 | function listWithMaxLen (list, maxLen) { 436 | const ELLIPSIS = chalk` {magenta + XX more}` 437 | const ELLIPSIS_LENGTH = stripAnsi(ELLIPSIS).length 438 | let str = '' 439 | for (let i = 0; i < list.length; i++) { 440 | const item = (i === 0 ? '' : ', ') + list[i] 441 | if (stripAnsi(str).length + stripAnsi(item).length >= maxLen - ELLIPSIS_LENGTH) { 442 | str += ELLIPSIS.replace('XX', list.length - i) 443 | break 444 | } 445 | str += item 446 | } 447 | return str 448 | } 449 | 450 | async function openDonateLinks (donateLinks) { 451 | for (const donateLink of donateLinks) { 452 | await open(donateLink, { url: true }) 453 | } 454 | console.log(chalk`\n{bold.yellow You are awesome!} 🌟`) 455 | } 456 | 457 | async function readDirectPkgNames () { 458 | const pkgPath = await pkgUp() 459 | 460 | if (pkgPath == null) { 461 | throw new Error( 462 | 'No package.json found. Run this in a Node.js project folder!' 463 | ) 464 | } 465 | 466 | const pkgStr = await readFileAsync(pkgPath, 'utf8') 467 | 468 | let pkg 469 | try { 470 | pkg = JSON.parse(pkgStr) 471 | } catch (err) { 472 | err.message = `Failed to parse package.json: ${err.message}` 473 | throw err 474 | } 475 | 476 | return [].concat( 477 | findDeps(pkg, 'dependencies'), 478 | findDeps(pkg, 'devDependencies'), 479 | findDeps(pkg, 'optionalDependencies') 480 | ) 481 | 482 | function findDeps (pkg, type) { 483 | return pkg[type] && typeof pkg[type] === 'object' 484 | ? Object.keys(pkg[type]) 485 | : [] 486 | } 487 | } 488 | 489 | function printInstructions () { 490 | const url = 'https://github.com/feross/thanks' 491 | const message = chalk`{bold Maintainers}: Add yourself to this list by sending a PR to {cyan ${url}}` 492 | console.log(message + '\n') 493 | } 494 | -------------------------------------------------------------------------------- /img/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feross/thanks/aeac99374ea4b6a70c4075223226b744608313eb/img/example.gif -------------------------------------------------------------------------------- /img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feross/thanks/aeac99374ea4b6a70c4075223226b744608313eb/img/icon.png -------------------------------------------------------------------------------- /img/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ]> 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | -------------------------------------------------------------------------------- /img/vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feross/thanks/aeac99374ea4b6a70c4075223226b744608313eb/img/vertical.png -------------------------------------------------------------------------------- /img/vertical.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ]> 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 31 | 45 | 46 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! thanks. MIT License. Feross Aboukhadijeh */ 2 | /* eslint-disable quote-props */ 3 | 4 | /* 5 | * npm username -> donate page 6 | * 7 | * Whenever a `thanks` user has a package owned by one of these authors in their 8 | * package tree, they will be prompted to donate. 9 | */ 10 | const authors = { 11 | akepinski: 'https://www.patreon.com/akepinski', 12 | amit_merchant: 'https://www.patreon.com/amitmerchant', 13 | andrewnez: 'https://en.liberapay.com/andrew/', 14 | anteriovieira: 'https://www.patreon.com/anteriovieira', 15 | antony: 'https://www.patreon.com/wirah', 16 | armaldio: 'https://www.paypal.me/armaldio', 17 | balupton: 'https://balupton.com/donate', 18 | bevacqua: 'https://www.patreon.com/bevacqua', 19 | bevry: 'https://bevry.me/donate', 20 | bevryme: 'https://bevry.me/donate', 21 | chinchang: 'https://www.patreon.com/kushagra', 22 | cipchk: 'https://www.patreon.com/cipchk', 23 | dannyfritz: 'https://www.patreon.com/dannyfritz', 24 | darcyclarke: 'https://www.patreon.com/darcyclarke', 25 | davidjbradshaw: 'https://www.paypal.me/davidjbradshaw', 26 | derhuerst: 'https://www.patreon.com/derhuerst', 27 | diegohaz: 'https://www.patreon.com/diegohaz', 28 | dmonad: 'https://github.com/sponsors/dmonad', 29 | eli: 'https://go.eligrey.com/donate', 30 | evantahler: 'https://www.paypal.me/evantahler', 31 | fabiosantoscode: 'https://www.patreon.com/fabiosantoscode', 32 | feross: 'https://www.patreon.com/feross', 33 | finwo: 'https://www.patreon.com/finwo', 34 | getify: 'https://www.patreon.com/getify', 35 | ganofins: 'https://www.patreon.com/ganofins', 36 | gr2m: 'https://railsgirlssummerofcode.org/campaign/', 37 | hacdias: 'https://www.patreon.com/hacdias', 38 | hueniverse: 'https://www.patreon.com/eranhammer', 39 | hughsk: 'https://hughsk.io/donate/', 40 | hzoo: 'https://www.patreon.com/henryzhu', 41 | jacksteamdev: 'https://ko-fi.com/jacksteam', 42 | jaredhanson: 'https://www.patreon.com/jaredhanson', 43 | jayphelps: 'https://www.patreon.com/jayphelps', 44 | 'johann-s': 'https://www.patreon.com/jservoire', 45 | johnjleider: 'https://www.patreon.com/vuetify', 46 | jpcote: 'https://beerpay.io/cotejp', 47 | juliangruber: 'https://www.patreon.com/juliangruber', 48 | kgryte: 'https://www.patreon.com/athan', 49 | korynunn: 'https://www.patreon.com/korynunn', 50 | litomore: 'https://www.paypal.me/LitoMore', 51 | loghorn: 'https://www.paypal.me/Loghorn', 52 | lmangani: 'https://bunq.me/qxip', 53 | lukechilds: 'https://blockchair.com/bitcoin/address/1LukeQU5jwebXbMLDVydeH4vFSobRV9rkj', 54 | maoberlehner: 'https://www.paypal.me/maoberlehner', 55 | marijn: 'https://www.patreon.com/marijn', 56 | mblarsen: 'https://flattr.com/@mblarsen', 57 | mmckegg: 'https://www.patreon.com/MattMcKegg', 58 | moox: 'https://liberapay.com/MoOx/', 59 | mpj: 'https://www.patreon.com/funfunfunction', 60 | mrahmadawais: 'https://buy.paddle.com/product/515568', 61 | mweststrate: 'https://www.paypal.me/michelweststrate', 62 | nickkaramoff: 'https://www.patreon.com/NickKaramoff', 63 | noffle: 'https://en.liberapay.com/noffle/', 64 | nzakas: 'https://www.paypal.me/nczonline', 65 | olstenlarck: 'https://www.paypal.me/tunnckoCore', 66 | ovhemert: 'https://www.patreon.com/ovhemert', 67 | panva: 'https://www.patreon.com/panva', 68 | paulcbetts: 'https://www.patreon.com/paulcbetts', 69 | pablopunk: 'https://www.paypal.me/pablopunk', 70 | posva: 'https://github.com/posva/thanks', 71 | qubyte: 'https://www.paypal.me/qubyte', 72 | rationalcoding: 'https://github.com/sponsors/t-mullen', 73 | rayzr: 'https://www.patreon.com/rayzr522', 74 | richardlitt: 'https://www.patreon.com/richlitt', 75 | riyadhalnur: 'https://www.paypal.me/riyadhalnur', 76 | rstoenescu: 'https://donate.quasar.dev', 77 | shama: 'https://www.patreon.com/shama', 78 | shellscape: 'https://www.patreon.com/shellscape', 79 | sindresorhus: 'https://www.patreon.com/sindresorhus', 80 | staltz: 'https://www.patreon.com/andrestaltz', 81 | steelbrain: 'https://www.patreon.com/steelbrain', 82 | softwaretailoring: 'https://www.paypal.me/softwaretailoring', 83 | thlorenz: 'https://www.patreon.com/thlorenz', 84 | tomchentw: 'https://www.patreon.com/tomchentw', 85 | typicode: 'https://www.patreon.com/typicode', 86 | yoshuawuyts: 'https://www.patreon.com/yoshuawuyts', 87 | yyx990803: 'https://www.patreon.com/evanyou', 88 | zeke: 'http://zeke.sikelianos.com/donations/', 89 | sergeylukin: 'https://www.paypal.me/smartcoding' 90 | } 91 | 92 | /* 93 | * npm organization name -> donate page 94 | * 95 | * Whenever a `thanks` user has a packages from one of these organizations in their 96 | * package tree, they will be prompted to donate. 97 | */ 98 | const organizations = { 99 | '11ty': 'https://opencollective.com/11ty', 100 | accounts: 'https://opencollective.com/accounts-js', 101 | babel: 'https://opencollective.com/babel', 102 | barba: 'https://opencollective.com/barbajs', 103 | bevry: 'https://opencollective.com/bevry', 104 | canner: 'https://opencollective.com/cannercms', 105 | codesandbox: 'https://opencollective.com/codesandbox', 106 | colmena: 'https://opencollective.com/colmena', 107 | compodoc: 'https://opencollective.com/compodoc', 108 | cycle: 'https://opencollective.com/cyclejs', 109 | docusaurus: 'https://opencollective.com/docusaurus', 110 | 'extend-chrome': 'https://ko-fi.com/jacksteam', 111 | emotion: 'https://opencollective.com/emotion', 112 | feathersjs: 'https://opencollective.com/feathers', 113 | hoodie: 'https://opencollective.com/hoodie', 114 | hyperapp: 'https://opencollective.com/hyperapp', 115 | jest: 'https://opencollective.com/jest', 116 | jitesoft: 'https://opencollective.com/jitesoft-open-source', 117 | jscad: 'https://opencollective.com/openjscad', 118 | klingon: 'https://opencollective.com/klingon', 119 | koa: 'https://opencollective.com/koajs', 120 | loadable: 'https://opencollective.com/loadable', 121 | 'material-ui': 'https://opencollective.com/material-ui', 122 | 'mdx-js': 'https://opencollective.com/unified', 123 | midwayjs: 'https://opencollective.com/midway', 124 | mocha: 'https://opencollective.com/mochajs', 125 | nestjs: 'https://opencollective.com/nest', 126 | ngrx: 'https://opencollective.com/ngrx', 127 | nivo: 'https://opencollective.com/nivo', 128 | nuxt: 'https://opencollective.com/nuxtjs', 129 | octolinker: 'https://opencollective.com/octolinker', 130 | phenomic: 'https://opencollective.com/phenomic', 131 | polka: 'https://opencollective.com/polka', 132 | popmotion: 'https://opencollective.com/popmotion', 133 | popperjs: 'https://opencollective.com/popperjs', 134 | prettier: 'https://opencollective.com/prettier', 135 | pwa: 'https://opencollective.com/pwa', 136 | qxip: 'https://bunq.me/qxip', 137 | quasar: 'https://donate.quasar.dev', 138 | 'react-native-firebase': 'https://opencollective.com/react-native-firebase', 139 | 'react-pdf': 'https://opencollective.com/react-pdf', 140 | 'redux-saga': 'https://opencollective.com/redux-saga', 141 | searchkit: 'https://opencollective.com/searchkit', 142 | sindresorhus: 'https://opencollective.com/sindresorhus', 143 | storybook: 'https://opencollective.com/storybook', 144 | svgr: 'https://opencollective.com/svgr', 145 | tarojs: 'https://opencollective.com/taro', 146 | turf: 'https://opencollective.com/turf', 147 | 'ui-grid': 'https://opencollective.com/ui-grid', 148 | warriorjs: 'https://opencollective.com/warriorjs', 149 | 'webpack-cli': 'https://opencollective.com/webpack' 150 | } 151 | 152 | /* 153 | * npm package name -> donate page 154 | * 155 | * Whenever a `thanks` user has one these exact packages in their package tree, 156 | * they will be prompted to donate. 157 | * 158 | * NOTE: If you have an npm organization, specify it above (see the `organizations` 159 | * variable above!). This gives maximum coverage versus listing each package below. 160 | */ 161 | const packages = { 162 | '30-seconds-of-code': 'https://opencollective.com/30-seconds-of-code', 163 | '@colmena/colmena': 'https://opencollective.com/colmena', 164 | '@feathersjs/feathers': 'https://opencollective.com/feathers', 165 | '@jscad/openjscad': 'https://opencollective.com/openjscad', 166 | '@ngrx/platform': 'https://opencollective.com/ngrx', 167 | '@reactivex/rxjs': 'https://opencollective.com/rxjs', 168 | '@react-pdf/renderer': 'https://opencollective.com/react-pdf', 169 | '@storybook/root': 'https://opencollective.com/storybook', 170 | 'acgn-stock': 'https://opencollective.com/acgn-stock', 171 | 'altair': 'https://opencollective.com/altair', 172 | 'angular-socialshare': 'https://opencollective.com/angular-socialshare', 173 | 'angular-starter': 'https://opencollective.com/angular-starter', 174 | 'AnsPress': 'https://opencollective.com/anspress', 175 | 'antd': 'https://opencollective.com/ant-design', 176 | 'aplayer': 'https://opencollective.com/aplayer', 177 | 'apollo-universal-starter-kit': 'https://opencollective.com/apollo-universal-starter-kit', 178 | 'aresume': 'https://opencollective.com/resume', 179 | 'aurelia-framework': 'https://opencollective.com/aurelia', 180 | 'ava': 'https://opencollective.com/ava', 181 | 'ava-ia': 'https://opencollective.com/ava-ia', 182 | 'awesome-mac': 'https://opencollective.com/awesome-mac', 183 | 'ax5ui-kernel': 'https://opencollective.com/ax5ui-kernel', 184 | 'axboot': 'https://opencollective.com/ax-boot-framework', 185 | 'beakerbrowser': 'https://opencollective.com/beaker', 186 | 'boost': 'https://opencollective.com/boostnoteio', 187 | 'bootstrap-ie8': 'https://www.paypal.me/coliff', 188 | 'bootstrap-table': 'https://opencollective.com/bootstrap-table', 189 | 'bootstrap-vue': 'https://opencollective.com/bootstrap-vue', 190 | 'bower': 'https://opencollective.com/bower', 191 | 'bundlesize': 'https://opencollective.com/bundlesize', 192 | 'buttercup-desktop': 'https://opencollective.com/buttercup', 193 | 'caption': 'https://opencollective.com/caption', 194 | 'cboard': 'https://opencollective.com/cboard', 195 | 'ccxt': 'https://opencollective.com/ccxt', 196 | 'cdnjs': 'https://opencollective.com/cdnjs', 197 | 'cerebro': 'https://opencollective.com/cerebro', 198 | 'cezerin': 'https://opencollective.com/cezerin', 199 | 'chai': 'https://opencollective.com/chaijs', 200 | 'cheerio': 'https://opencollective.com/cheerio', 201 | 'choo': 'https://opencollective.com/choo', 202 | 'cloudcmd': 'https://opencollective.com/cloudcmd', 203 | 'CNodeRN': 'https://opencollective.com/cnodern', 204 | 'cockpit-next': 'https://opencollective.com/cockpit', 205 | 'color-space': 'https://opencollective.com/color-space', 206 | 'colyseus': 'https://opencollective.com/colyseus', 207 | 'commitizen': 'https://opencollective.com/commitizen', 208 | 'ConfigurableMapViewerCMV': 'https://opencollective.com/cmv-app', 209 | 'cypress': 'https://opencollective.com/cypressio', 210 | 'dat': 'https://opencollective.com/dat', 211 | 'DataServices': 'https://opencollective.com/cloudboost', 212 | 'date-fns': 'https://opencollective.com/date-fns', 213 | 'debug': 'https://opencollective.com/debug', 214 | 'dep': 'https://opencollective.com/dep', 215 | 'dim': 'https://opencollective.com/dim', 216 | 'discord.js': 'https://opencollective.com/discordjs', 217 | 'docker.io': 'https://opencollective.com/dockerio', 218 | 'docsify': 'https://opencollective.com/docsify', 219 | 'document-register-element': 'https://opencollective.com/document-register-element', 220 | 'docute': 'https://opencollective.com/docute', 221 | 'dplayer': 'https://opencollective.com/dplayer', 222 | 'echoes-player': 'https://opencollective.com/echoes-player', 223 | 'electron-react-boilerplate': 'https://opencollective.com/electron-react-boilerplate', 224 | 'element-ui': 'https://opencollective.com/element', 225 | 'ellie': 'https://opencollective.com/ellie', 226 | 'eme': 'https://opencollective.com/eme', 227 | 'emoji-url-shortener': 'https://opencollective.com/url-shortener', 228 | 'erxes': 'https://opencollective.com/erxes', 229 | 'esdiscuss.org': 'https://opencollective.com/esdiscuss', 230 | 'esnextbin': 'https://opencollective.com/esnextbin', 231 | 'faker': 'https://opencollective.com/fakerjs', 232 | 'fast-xml-parser': 'https://opencollective.com/fast-xml-parser', 233 | 'ferment': 'https://opencollective.com/lolashare', 234 | 'Fireideaz': 'https://opencollective.com/distributed', 235 | 'firekylin': 'https://opencollective.com/firekylin', 236 | 'flatpickr': 'https://opencollective.com/flatpickr', 237 | 'fontplop': 'https://opencollective.com/fontplop', 238 | 'front-end-checklist': 'https://opencollective.com/front-end-checklist', 239 | 'fuse-box': 'https://opencollective.com/fuse-box', 240 | 'generator-jhipster': 'https://opencollective.com/generator-jhipster', 241 | 'gh-badges': 'https://opencollective.com/shields', 242 | 'ghost': 'https://opencollective.com/ghost', 243 | 'gitpoint': 'https://opencollective.com/git-point', 244 | 'go-plus': 'https://opencollective.com/go-plus', 245 | 'google-play-music-desktop-player': 'https://opencollective.com/google-play-music-desktop-player-unofficial-', 246 | 'grapesjs': 'https://opencollective.com/grapesjs', 247 | 'griddle-react': 'https://opencollective.com/griddle', 248 | 'Grow-IoT': 'https://opencollective.com/grow-iot', 249 | 'gulp': 'https://opencollective.com/gulpjs', 250 | 'gulp-cli': 'https://opencollective.com/gulpjs', 251 | 'hamsters.js': 'https://opencollective.com/hamstersjs', 252 | 'hedron': 'https://opencollective.com/hedron', 253 | 'hexo': 'https://opencollective.com/hexo', 254 | 'hoodie': 'https://opencollective.com/hoodie', 255 | 'hoverboard': 'https://opencollective.com/hoverboard', 256 | 'hyperhtml': 'https://opencollective.com/hyperhtml', 257 | 'icestudio': 'https://opencollective.com/icestudio', 258 | 'idyll': 'https://opencollective.com/idyll', 259 | 'ifme': 'https://opencollective.com/ifme', 260 | 'immer': 'https://opencollective.com/mobx', 261 | 'inferno-build': 'https://opencollective.com/inferno', 262 | 'ion-rangeslider': 'https://opencollective.com/ionrangeslider', 263 | 'ion-sound': 'https://opencollective.com/ionsound', 264 | 'javascript-obfuscator': 'https://opencollective.com/javascript-obfuscator', 265 | 'jqplay': 'https://opencollective.com/jqplay', 266 | 'jquery-jsonview': 'https://opencollective.com/jquery-jsonview', 267 | 'jquery.adaptive-backgrounds': 'https://opencollective.com/jquery-adaptive-background', 268 | 'jsbin': 'https://opencollective.com/jsbin', 269 | 'jss': 'https://opencollective.com/jss', 270 | 'Kaku': 'https://opencollective.com/kaku', 271 | 'karma-typescript': 'https://opencollective.com/karma-typescript', 272 | 'kea': 'https://opencollective.com/kea', 273 | 'koa': 'https://opencollective.com/koajs', 274 | 'lad': 'https://opencollective.com/lad', 275 | 'lazy.ai': 'https://opencollective.com/lazy-bot', 276 | 'lem': 'https://opencollective.com/lem', 277 | 'lethargy': 'https://opencollective.com/lethargy', 278 | 'levelup': 'https://opencollective.com/level', 279 | 'linter': 'https://opencollective.com/linter', 280 | 'lumo': 'https://opencollective.com/lumo', 281 | 'material-ui': 'https://opencollective.com/material-ui', 282 | 'material-ui-build': 'https://opencollective.com/material-ui', 283 | 'materialize-css': 'https://www.patreon.com/materialize', 284 | 'Matterwiki': 'https://opencollective.com/matterwiki', 285 | 'mimic': 'https://opencollective.com/mimic', 286 | 'mobx': 'https://opencollective.com/mobx', 287 | 'mobx-state-tree': 'https://opencollective.com/mobx', 288 | 'mocha': 'https://opencollective.com/mochajs', 289 | 'modular-admin-html': 'https://opencollective.com/modular-admin', 290 | 'mongoose': 'https://opencollective.com/mongoose', 291 | 'moro': 'https://opencollective.com/moro', 292 | 'mup': 'https://opencollective.com/meteor-up', 293 | 'ndm': 'https://opencollective.com/ndm', 294 | 'nearley': 'https://nearley.js.org/#give-to-nearley', 295 | 'nestjs': 'https://opencollective.com/nest', 296 | 'nexe': 'https://opencollective.com/nexe', 297 | 'ng2-date-picker': 'https://opencollective.com/angular-datepicker', 298 | 'ngx-infinite-scroll': 'https://opencollective.com/ngx-infinite-scroll', 299 | 'nightwatch': 'https://opencollective.com/nightwatch', 300 | 'noble': 'https://opencollective.com/noble', 301 | 'nodedata': 'https://opencollective.com/node-data', 302 | 'nodemailer': 'https://opencollective.com/nodemailer', 303 | 'nodemon': 'https://opencollective.com/nodemon', 304 | 'node-fetch': 'https://opencollective.com/node-fetch', 305 | 'nuxt': 'https://opencollective.com/nuxtjs', 306 | 'nwitter': 'https://opencollective.com/node-twitter', 307 | 'OctoLinker': 'https://opencollective.com/octolinker', 308 | 'octotree': 'https://opencollective.com/octotree', 309 | 'offline-plugin': 'https://opencollective.com/offline-plugin', 310 | 'openfl': 'https://opencollective.com/openfl', 311 | 'openlayers': 'https://opencollective.com/openlayers', 312 | 'parcel': 'https://opencollective.com/parcel', 313 | 'parcel-bundler': 'https://opencollective.com/parcel', 314 | 'parle': 'https://opencollective.com/parle', 315 | 'parse-server': 'https://opencollective.com/parse-server', 316 | 'phenomic': 'https://opencollective.com/phenomic', 317 | 'pickadate': 'https://opencollective.com/pickadatejs', 318 | 'poi': 'https://opencollective.com/poi', 319 | 'pokeapi': 'https://opencollective.com/pokeapi', 320 | 'postgraphql': 'https://opencollective.com/postgraphql', 321 | 'preact': 'https://opencollective.com/preact', 322 | 'pug': 'https://opencollective.com/pug', 323 | 'pug-monorepo': 'https://opencollective.com/pug', 324 | 'ramda-adjunct': 'https://opencollective.com/ramda-adjunct', 325 | 'ran-boilerplate': 'https://opencollective.com/ran', 326 | 'react-ace': 'https://opencollective.com/react-ace', 327 | 'react-boilerplate': 'https://opencollective.com/react-boilerplate', 328 | 'react-dropzone': 'https://opencollective.com/react-dropzone', 329 | 'react-final-form': 'https://opencollective.com/final-form', 330 | 'react-native-camera': 'https://opencollective.com/react-native-camera', 331 | 'react-native-debugger': 'https://opencollective.com/react-native-debugger', 332 | 'react-native-elements': 'https://opencollective.com/react-native-elements', 333 | 'react-native-firebase': 'https://opencollective.com/react-native-firebase', 334 | 'react-native-image-crop-picker': 'https://opencollective.com/react-native-image-crop-picker', 335 | 'react-native-masonry': 'https://opencollective.com/react-native-masonry', 336 | 'react-native-responsive-grid': 'https://opencollective.com/react-native-responsive-grid', 337 | 'react-native-router-flux': 'https://opencollective.com/react-native-router-flux', 338 | 'react-on-rails': 'https://opencollective.com/react-on-rails', 339 | 'react-redux-firebase': 'https://opencollective.com/react-redux-firebase', 340 | 'react-slick': 'https://opencollective.com/react-slick', 341 | 'react-styleguidist': 'https://opencollective.com/styleguidist', 342 | 'react-toolbox': 'https://opencollective.com/react-toolbox', 343 | 'reactabular': 'https://opencollective.com/reactabular', 344 | 'ReactPWA': 'https://opencollective.com/react-pwa', 345 | 'ream': 'https://opencollective.com/ream', 346 | 'redom': 'https://opencollective.com/redom', 347 | 'redux-devtools-extension': 'https://opencollective.com/redux-devtools-extension', 348 | 'redux-saga': 'https://opencollective.com/redux-saga', 349 | 'rehype': 'https://opencollective.com/unified', 350 | 'relax': 'https://opencollective.com/relax', 351 | 'remark': 'https://opencollective.com/unified', 352 | 'remotedev-redux-devtools-extension': 'https://opencollective.com/redux-devtools-extension', 353 | 'retext': 'https://opencollective.com/unified', 354 | 'research-engine': 'https://opencollective.com/research-engine', 355 | 'riot': 'https://opencollective.com/riot', 356 | 'rollup': 'https://opencollective.com/rollup', 357 | 'sage': 'https://opencollective.com/sage', 358 | 'satellizer': 'https://opencollective.com/satellizer', 359 | 'selection-sharer': 'https://opencollective.com/selection-sharer', 360 | 'serializr': 'https://opencollective.com/mobx', 361 | 'shapeshifter': 'https://opencollective.com/shapeshifter', 362 | 'sierra-library': 'https://opencollective.com/sierra', 363 | 'sinon': 'https://opencollective.com/sinon', 364 | 'Sizzy': 'https://opencollective.com/sizzy', 365 | 'skatejs-monorepo': 'https://opencollective.com/skatejs', 366 | 'sketchpad': 'https://opencollective.com/sketchpad', 367 | 'slim-js': 'https://www.patreon.com/slimjs', 368 | 'socket.io': 'https://opencollective.com/socketio', 369 | 'split.js': 'https://opencollective.com/splitjs', 370 | 'strapi': 'https://opencollective.com/strapi', 371 | 'streetmix': 'https://opencollective.com/streetmix', 372 | 'strider': 'https://opencollective.com/strider', 373 | 'styled-components': 'https://opencollective.com/styled-components', 374 | 'stylelint': 'https://opencollective.com/stylelint', 375 | 'surfbird': 'https://opencollective.com/surfbird', 376 | 'sweetalert': 'https://opencollective.com/sweetalert', 377 | 't-scroll': 'https://opencollective.com/t-scroll', 378 | 'tachyons': 'https://opencollective.com/tachyons', 379 | 'telegraf': 'https://opencollective.com/telegraf', 380 | 'TellForm': 'https://opencollective.com/tellform', 381 | 'tipbox': 'https://opencollective.com/tipbox', 382 | 'trails': 'https://opencollective.com/trails', 383 | 'typeorm': 'https://opencollective.com/typeorm', 384 | 'ui-grid': 'https://opencollective.com/ui-grid', 385 | 'unified': 'https://opencollective.com/unified', 386 | 'universalviewer': 'https://opencollective.com/universalviewer', 387 | 'verdaccio': 'https://opencollective.com/verdaccio', 388 | 'vfile': 'https://opencollective.com/unified', 389 | 'vim-cheat-sheet': 'https://opencollective.com/vim-cheat-sheet', 390 | 'vsc-material-theme': 'https://opencollective.com/vsc-material-theme', 391 | 'vue': 'https://opencollective.com/vuejs', 392 | 'vue-admin': 'https://opencollective.com/vue-admin', 393 | 'vue-js-modal': 'https://opencollective.com/vue-js-modal', 394 | 'Vulcan': 'https://opencollective.com/telescope', 395 | 'vux': 'https://opencollective.com/vux', 396 | 'wallabag': 'https://opencollective.com/wallabag', 397 | 'weather-10kb': 'https://opencollective.com/weather-10kb', 398 | 'webpack': 'https://opencollective.com/webpack', 399 | 'webslides': 'https://opencollective.com/webslides', 400 | 'whs': 'https://opencollective.com/whitestormjs', 401 | 'wiki': 'https://opencollective.com/wikijs', 402 | 'WireFlow': 'https://opencollective.com/wireflow', 403 | 'WPGulp': 'https://opencollective.com/wpgulp', 404 | 'xss-listener': 'https://opencollective.com/xss-listener', 405 | 'yo': 'https://opencollective.com/yeoman' 406 | } 407 | 408 | module.exports = { authors, organizations, packages } 409 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thanks", 3 | "description": "Give thanks to the open source maintainers you depend on!", 4 | "version": "2.3.0", 5 | "author": { 6 | "name": "Feross Aboukhadijeh", 7 | "email": "feross@feross.org", 8 | "url": "https://feross.org" 9 | }, 10 | "bin": "bin/cmd.js", 11 | "bugs": { 12 | "url": "https://github.com/feross/thanks/issues" 13 | }, 14 | "dependencies": { 15 | "chalk": "^4.1.0", 16 | "common-tags": "^1.8.0", 17 | "got": "^11.5.1", 18 | "minimist": "^1.2.5", 19 | "npm-registry-client": "^8.6.0", 20 | "open": "^7.0.4", 21 | "ora": "^4.0.4", 22 | "pify": "^5.0.0", 23 | "pkg-dir": "^4.2.0", 24 | "pkg-up": "^3.1.0", 25 | "prompt-confirm": "^2.0.4", 26 | "read-package-tree": "^5.3.1", 27 | "registry-auth-token": "^4.2.0", 28 | "registry-url": "^5.1.0", 29 | "strip-ansi": "^6.0.0", 30 | "term-size": "^2.2.0", 31 | "text-table": "^0.2.0" 32 | }, 33 | "devDependencies": { 34 | "standard": "*" 35 | }, 36 | "engines": { 37 | "node": ">=8" 38 | }, 39 | "keywords": [ 40 | "donate", 41 | "donor", 42 | "earn", 43 | "fund", 44 | "funding", 45 | "open source", 46 | "patreon", 47 | "support", 48 | "sustain", 49 | "sustainability", 50 | "thank you", 51 | "thanks" 52 | ], 53 | "license": "MIT", 54 | "main": "index.js", 55 | "repository": { 56 | "type": "git", 57 | "url": "git://github.com/feross/thanks.git" 58 | }, 59 | "scripts": { 60 | "test": "standard && ./bin/cmd.js --no-open" 61 | }, 62 | "funding": [ 63 | { 64 | "type": "github", 65 | "url": "https://github.com/sponsors/feross" 66 | }, 67 | { 68 | "type": "patreon", 69 | "url": "https://www.patreon.com/feross" 70 | }, 71 | { 72 | "type": "consulting", 73 | "url": "https://feross.org/support" 74 | } 75 | ] 76 | } 77 | --------------------------------------------------------------------------------