├── .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 |
3 |
4 |
5 |
6 | 🙌 Give thanks to the open source maintainers you depend on! ✨
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | 
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 |
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 |
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 |
--------------------------------------------------------------------------------