mirror of https://github.com/feross/funding
Compare commits
31 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
56f8887088 | |
![]() |
9460c64a43 | |
![]() |
58b090c51c | |
![]() |
b12b311554 | |
![]() |
4a04573bc8 | |
![]() |
7238cd97d6 | |
![]() |
d07b9af505 | |
![]() |
03937d3f11 | |
![]() |
2485ab553e | |
![]() |
8115da8aa9 | |
![]() |
427bb8ffb6 | |
![]() |
356655d985 | |
![]() |
705e7c9c23 | |
![]() |
67746f78f2 | |
![]() |
ebce9a2a28 | |
![]() |
4593e982d6 | |
![]() |
08601f8c52 | |
![]() |
b9a3b3802b | |
![]() |
3134b34041 | |
![]() |
4399a0950a | |
![]() |
237caca5b8 | |
![]() |
b24ccba2bb | |
![]() |
c5269f1fcb | |
![]() |
9627cd338b | |
![]() |
b2fbc11692 | |
![]() |
2820d65c4a | |
![]() |
7e286cbbed | |
![]() |
687ff1c077 | |
![]() |
748b703f62 | |
![]() |
7eea1cb993 | |
![]() |
86fbb555b2 |
|
@ -1,2 +1,3 @@
|
|||
.travis.yml
|
||||
test/
|
||||
tools/
|
||||
|
|
20
README.md
20
README.md
|
@ -11,25 +11,29 @@
|
|||
[standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg
|
||||
[standard-url]: https://standardjs.com
|
||||
|
||||
### UPDATE: The experiment is over – Feross posted [a recap](https://feross.org/funding-experiment-recap/) on his blog
|
||||
|
||||
This is an open source funding experiment! The current model of sustaining open source is not working. We desperately need more experimentation. This is one such experiment.
|
||||
|
||||
## Usage
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install funding
|
||||
```
|
||||
|
||||
### UPDATE: The experiment is over – Feross posted [a recap](https://feross.org/funding-experiment-recap/) on his blog
|
||||
|
||||
## What is this?
|
||||
|
||||
This is an open source funding experiment! ✨
|
||||
|
||||
Whenever users install open source software, this package will display a message from a company that supports open source. Currently, these are [Linode](https://welcome.linode.com/standardjs) and [LogRocket](https://logrocket.com/term). The sponsorship pays directly for maintainer time. That is, writing new features, fixing bugs, answering user questions, and improving documentation.
|
||||
Whenever users install open source software, this package will display a message from a company that supports open source. The sponsorship pays directly for maintainer time. That is, writing new features, fixing bugs, answering user questions, and improving documentation.
|
||||
|
||||
The goal is to make sure that packages are well-maintained now and for the foreseeable future, with regular releases, improved reliability, and timely security patches. Healthy open source packages benefit users and maintainers alike.
|
||||
|
||||
## What does this code do?
|
||||
|
||||
You can take a look! All the code is open source in this GitHub repository. Essentially, it calls `console.log()` on some text. There is no tracking, data collecting, or unexpected behavior. You can look at the code to verify – indeed, this is the beauty of open source!
|
||||
You can take a look! All the code is open source in this GitHub repository. Essentially, it calls `console.log()` on some text. **There is no tracking or data collecting — and it will always stay this way.** You can look at the code to verify – indeed, this is the beauty of open source!
|
||||
|
||||
## Where is this experiment running?
|
||||
|
||||
|
@ -37,6 +41,8 @@ This experiment is currently running on a few open source projects that [Feross]
|
|||
|
||||
- [`standard`](https://standardjs.com)
|
||||
|
||||
### UPDATE: The experiment is over – Feross posted [a recap](https://feross.org/funding-experiment-recap/) on his blog
|
||||
|
||||
## Who is Feross?
|
||||
|
||||
Hey there, I'm Feross!
|
||||
|
@ -62,3 +68,11 @@ The funds raised so far ($2,000) have paid for Feross's time to [release Standar
|
|||
## Where can I provide feedback about this experiment?
|
||||
|
||||
You can open an issue. But please be kind. I'm a human with feelings. ❤️
|
||||
|
||||
## How can I disable this?
|
||||
|
||||
Just to be super clear: **This package does no tracking or data collecting — and it will always stay this way.** It's just a fancy `console.log()`.
|
||||
|
||||
If you support open source through direct contributions, donations, or however else you see fit, you can permanently silence `funding` by adding an environment variable `OPEN_SOURCE_CONTRIBUTOR=true` to your terminal environment.
|
||||
|
||||
Note, `funding` also respects npm's `loglevel` setting, so e.g. `npm install --silent` and `npm install --quiet` will be respected.
|
||||
|
|
|
@ -15,7 +15,7 @@ try {
|
|||
|
||||
try {
|
||||
if (funding) {
|
||||
funding.printRandomMessage()
|
||||
funding.printMessage()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err.stack || err.message || err)
|
||||
|
|
80
index.js
80
index.js
|
@ -1,20 +1,27 @@
|
|||
const boxen = require('boxen')
|
||||
const chalk = require('chalk')
|
||||
|
||||
const detect = require('./lib/detect')
|
||||
const wrap = require('./lib/wrap')
|
||||
const check = require('./lib/check')
|
||||
const {
|
||||
isHyper,
|
||||
isITerm,
|
||||
isCI,
|
||||
isSilentMode
|
||||
} = require('./lib/detect')
|
||||
|
||||
const { isShownRecently, markShown } = require('./lib/limit')
|
||||
|
||||
const { checkMessage } = require('./lib/check')
|
||||
const messages = require('./messages.json')
|
||||
const wrap = require('./lib/wrap')
|
||||
|
||||
function formatTitle (title) {
|
||||
title = wrap(title)
|
||||
|
||||
if (!detect.isTravis()) {
|
||||
if (!isCI()) {
|
||||
title = chalk.black(title)
|
||||
}
|
||||
|
||||
if (!detect.isITerm() && !detect.isHyper()) {
|
||||
if (!isHyper() && !isITerm()) {
|
||||
title = chalk.bold(title)
|
||||
}
|
||||
|
||||
|
@ -29,7 +36,7 @@ function formatText (text) {
|
|||
(match, url) => chalk.blue.underline(url)
|
||||
)
|
||||
|
||||
if (!detect.isTravis()) {
|
||||
if (!isCI()) {
|
||||
text = chalk.black(text)
|
||||
}
|
||||
|
||||
|
@ -49,7 +56,16 @@ function formatMessage (message) {
|
|||
|
||||
const opts = {
|
||||
align: 'center',
|
||||
borderStyle: {
|
||||
topLeft: ' ',
|
||||
topRight: ' ',
|
||||
bottomLeft: ' ',
|
||||
bottomRight: ' ',
|
||||
horizontal: ' ',
|
||||
vertical: ' '
|
||||
},
|
||||
float: 'center',
|
||||
margin: 0,
|
||||
padding: {
|
||||
top: 1,
|
||||
right: 4,
|
||||
|
@ -58,32 +74,28 @@ function formatMessage (message) {
|
|||
}
|
||||
}
|
||||
|
||||
if (detect.isTravis()) {
|
||||
if (!isCI()) {
|
||||
Object.assign(opts, {
|
||||
borderColor: 'green',
|
||||
borderStyle: 'bold',
|
||||
margin: 1
|
||||
})
|
||||
} else {
|
||||
Object.assign(opts, {
|
||||
borderStyle: {
|
||||
topLeft: ' ',
|
||||
topRight: ' ',
|
||||
bottomLeft: ' ',
|
||||
bottomRight: ' ',
|
||||
horizontal: ' ',
|
||||
vertical: ' '
|
||||
},
|
||||
backgroundColor: 'white',
|
||||
margin: 0
|
||||
backgroundColor: 'white'
|
||||
})
|
||||
}
|
||||
|
||||
return boxen(coloredMessage, opts)
|
||||
}
|
||||
|
||||
function checkMessage (message) {
|
||||
const { title, text, url } = message
|
||||
function printMessage () {
|
||||
// Do not print message when npm is run in silent mode
|
||||
if (isSilentMode()) return
|
||||
|
||||
// Do not print message when one has been shown recently
|
||||
if (isShownRecently()) return
|
||||
|
||||
// Skip running if no messages are available
|
||||
if (messages.length === 0) return
|
||||
|
||||
// Select a random message
|
||||
const i = Math.floor(Math.random() * messages.length)
|
||||
const message = messages[i]
|
||||
|
||||
// Check if the strings are safe to print to the terminal. Specifically, the
|
||||
// string should be plain ASCII, excluding control characters. This is
|
||||
|
@ -92,22 +104,16 @@ function checkMessage (message) {
|
|||
// strings at package publish time (see test/messages.js). But it doesn't hurt
|
||||
// to check again in the client and assert that messages are plain ASCII. This
|
||||
// is the security principle of defense-in-depth.
|
||||
check(title)
|
||||
check(text)
|
||||
check(url)
|
||||
}
|
||||
|
||||
function printRandomMessage () {
|
||||
const i = Math.floor(Math.random() * messages.length)
|
||||
const message = messages[i]
|
||||
|
||||
checkMessage(message)
|
||||
|
||||
// Format the message and print it
|
||||
const formattedMessage = formatMessage(message)
|
||||
console.log(formattedMessage + '\n')
|
||||
|
||||
// Limit the frequency that messages are shown
|
||||
markShown()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatMessage,
|
||||
checkMessage,
|
||||
printRandomMessage
|
||||
printMessage
|
||||
}
|
||||
|
|
13
lib/check.js
13
lib/check.js
|
@ -18,4 +18,15 @@ function checkString (str) {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = checkString
|
||||
function checkMessage (message) {
|
||||
const { title, text, url } = message
|
||||
|
||||
checkString(title)
|
||||
checkString(text)
|
||||
checkString(url)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
checkString,
|
||||
checkMessage
|
||||
}
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
/**
|
||||
* Functions to detect which Terminal emulator is in use.
|
||||
* Functions to detect information about the environment, e.g. which Terminal
|
||||
* emulator is in use, or whether silent mode is enabled.
|
||||
*/
|
||||
|
||||
const ciInfo = require('ci-info')
|
||||
|
||||
const TERM_PROGRAM = process.env.TERM_PROGRAM
|
||||
const {
|
||||
TERM_PROGRAM,
|
||||
npm_config_loglevel: NPM_CONFIG_LOGLEVEL,
|
||||
OPEN_SOURCE_CONTRIBUTOR
|
||||
} = process.env
|
||||
|
||||
// Is Hyper (Mac)?
|
||||
const isHyper = () => TERM_PROGRAM === 'Hyper'
|
||||
|
@ -15,12 +20,24 @@ const isITerm = () => TERM_PROGRAM === 'iTerm.app'
|
|||
// Is Terminal.app (Mac)?
|
||||
const isTerminalApp = () => TERM_PROGRAM === 'Apple_Terminal'
|
||||
|
||||
// Is Travis (CI)?
|
||||
const isTravis = () => ciInfo.TRAVIS
|
||||
// Is CI?
|
||||
const isCI = () => ciInfo.isCI
|
||||
|
||||
// Is silent mode enabled?
|
||||
const isSilentMode = () => (
|
||||
['silent', 'error'].includes(NPM_CONFIG_LOGLEVEL) ||
|
||||
(NPM_CONFIG_LOGLEVEL === 'warn' && !process.version.startsWith('v6.')) ||
|
||||
isEnabled(OPEN_SOURCE_CONTRIBUTOR)
|
||||
)
|
||||
|
||||
function isEnabled (value) {
|
||||
return !!value && value !== '0' && value !== 'false'
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isHyper,
|
||||
isITerm,
|
||||
isTerminalApp,
|
||||
isTravis
|
||||
isCI,
|
||||
isSilentMode
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* Functions to limit the frequency that messages are shown.
|
||||
*/
|
||||
|
||||
const { tmpdir } = require('os')
|
||||
const { statSync, unlinkSync, writeFileSync } = require('fs')
|
||||
const { join } = require('path')
|
||||
|
||||
const LIMIT_FILE_PATH = join(tmpdir(), 'funding-message-shown')
|
||||
const LIMIT_TIMEOUT = 60 * 1000 // 1 minute
|
||||
|
||||
function isShownRecently () {
|
||||
try {
|
||||
const { mtime: lastShown } = statSync(LIMIT_FILE_PATH)
|
||||
return Date.now() - lastShown < LIMIT_TIMEOUT
|
||||
} catch (e) {}
|
||||
return false
|
||||
}
|
||||
|
||||
function markShown () {
|
||||
try {
|
||||
writeFileSync(LIMIT_FILE_PATH, '')
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
// Only used in tests
|
||||
function clearShown () {
|
||||
try {
|
||||
unlinkSync(LIMIT_FILE_PATH)
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isShownRecently,
|
||||
markShown,
|
||||
clearShown
|
||||
}
|
|
@ -1,12 +1,7 @@
|
|||
[
|
||||
{
|
||||
"title": "Linode cloud computing",
|
||||
"text": "Deploy a server in seconds with your choice of Linux distro, resources, and host location. For a $20 credit, enter promo code STANDARDJS19 at sign up.",
|
||||
"url": "https://welcome.linode.com/standardjs"
|
||||
},
|
||||
{
|
||||
"title": "LogRocket",
|
||||
"text": "Stop guessing why bugs happen. LogRocket lets you replay what users do on your web app or website, helping you reproduce bugs and fix issues faster.",
|
||||
"url": "https://logrocket.com/term"
|
||||
"title": "npm install funding",
|
||||
"text": "I appreciate the thoughtful discussion and feedback from the community. I shared some thoughts about how this experiment went from my perspective.",
|
||||
"url": "https://feross.org/funding-experiment-recap/"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "funding",
|
||||
"description": "Get open source maintainers paid",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.9",
|
||||
"author": {
|
||||
"name": "Feross Aboukhadijeh",
|
||||
"email": "feross@feross.org",
|
||||
|
@ -22,7 +22,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"standard": "*",
|
||||
"tape": "^4.11.0"
|
||||
"tape": "^5.0.0"
|
||||
},
|
||||
"homepage": "https://github.com/feross/funding",
|
||||
"keywords": [
|
||||
|
@ -39,7 +39,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"postinstall": "node bin/funding.js",
|
||||
"start": "node bin/funding.js",
|
||||
"test": "standard && node bin/funding.js && tape test/*.js"
|
||||
"start": "node tools/clear.js && node bin/funding.js",
|
||||
"test": "standard && npm start && tape test/*.js"
|
||||
}
|
||||
}
|
||||
|
|
132
test/check.js
132
test/check.js
|
@ -1,146 +1,146 @@
|
|||
const test = require('tape')
|
||||
const check = require('../lib/check')
|
||||
const { checkString } = require('../lib/check')
|
||||
|
||||
test('check() accepts valid strings', t => {
|
||||
test('checkString() accepts valid strings', t => {
|
||||
t.doesNotThrow(() => {
|
||||
check('')
|
||||
checkString('')
|
||||
})
|
||||
t.doesNotThrow(() => {
|
||||
check('support open source')
|
||||
checkString('support open source')
|
||||
})
|
||||
t.doesNotThrow(() => {
|
||||
check('support open source\nbe a part of history')
|
||||
checkString('support open source\nbe a part of history')
|
||||
})
|
||||
t.doesNotThrow(() => {
|
||||
check('support open source\nbe a part of history\nmaintainers unite')
|
||||
checkString('support open source\nbe a part of history\nmaintainers unite')
|
||||
})
|
||||
t.doesNotThrow(() => {
|
||||
check('!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
|
||||
checkString('!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
|
||||
})
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('check() behaves as expected on first 127 characters', t => {
|
||||
test('checkString() behaves as expected on first 127 characters', t => {
|
||||
// control characters not allowed
|
||||
t.throws(() => { check('\u0000') })
|
||||
t.throws(() => { check('\u0001') })
|
||||
t.throws(() => { check('\u0002') })
|
||||
t.throws(() => { check('\u0003') })
|
||||
t.throws(() => { check('\u0004') })
|
||||
t.throws(() => { check('\u0005') })
|
||||
t.throws(() => { check('\u0006') })
|
||||
t.throws(() => { check('\u0007') })
|
||||
t.throws(() => { check('\u0008') })
|
||||
t.throws(() => { check('\u0009') })
|
||||
t.throws(() => { checkString('\u0000') })
|
||||
t.throws(() => { checkString('\u0001') })
|
||||
t.throws(() => { checkString('\u0002') })
|
||||
t.throws(() => { checkString('\u0003') })
|
||||
t.throws(() => { checkString('\u0004') })
|
||||
t.throws(() => { checkString('\u0005') })
|
||||
t.throws(() => { checkString('\u0006') })
|
||||
t.throws(() => { checkString('\u0007') })
|
||||
t.throws(() => { checkString('\u0008') })
|
||||
t.throws(() => { checkString('\u0009') })
|
||||
|
||||
// newline is allowed
|
||||
t.doesNotThrow(() => { check('\u000a') })
|
||||
t.doesNotThrow(() => { checkString('\u000a') })
|
||||
|
||||
// control characters not allowed
|
||||
t.throws(() => { check('\u000b') })
|
||||
t.throws(() => { check('\u000c') })
|
||||
t.throws(() => { check('\u000d') })
|
||||
t.throws(() => { check('\u000e') })
|
||||
t.throws(() => { check('\u000f') })
|
||||
t.throws(() => { check('\u0010') })
|
||||
t.throws(() => { check('\u0011') })
|
||||
t.throws(() => { check('\u0012') })
|
||||
t.throws(() => { check('\u0013') })
|
||||
t.throws(() => { check('\u0014') })
|
||||
t.throws(() => { check('\u0015') })
|
||||
t.throws(() => { check('\u0016') })
|
||||
t.throws(() => { check('\u0017') })
|
||||
t.throws(() => { check('\u0018') })
|
||||
t.throws(() => { check('\u0019') })
|
||||
t.throws(() => { check('\u001a') })
|
||||
t.throws(() => { check('\u001b') })
|
||||
t.throws(() => { check('\u001c') })
|
||||
t.throws(() => { check('\u001d') })
|
||||
t.throws(() => { check('\u001e') })
|
||||
t.throws(() => { check('\u001f') })
|
||||
t.throws(() => { checkString('\u000b') })
|
||||
t.throws(() => { checkString('\u000c') })
|
||||
t.throws(() => { checkString('\u000d') })
|
||||
t.throws(() => { checkString('\u000e') })
|
||||
t.throws(() => { checkString('\u000f') })
|
||||
t.throws(() => { checkString('\u0010') })
|
||||
t.throws(() => { checkString('\u0011') })
|
||||
t.throws(() => { checkString('\u0012') })
|
||||
t.throws(() => { checkString('\u0013') })
|
||||
t.throws(() => { checkString('\u0014') })
|
||||
t.throws(() => { checkString('\u0015') })
|
||||
t.throws(() => { checkString('\u0016') })
|
||||
t.throws(() => { checkString('\u0017') })
|
||||
t.throws(() => { checkString('\u0018') })
|
||||
t.throws(() => { checkString('\u0019') })
|
||||
t.throws(() => { checkString('\u001a') })
|
||||
t.throws(() => { checkString('\u001b') })
|
||||
t.throws(() => { checkString('\u001c') })
|
||||
t.throws(() => { checkString('\u001d') })
|
||||
t.throws(() => { checkString('\u001e') })
|
||||
t.throws(() => { checkString('\u001f') })
|
||||
|
||||
// normal characters are allowed
|
||||
for (let i = 0x20; i < 0x7f; i++) {
|
||||
t.doesNotThrow(() => { check(Buffer.from([i]).toString()) })
|
||||
t.doesNotThrow(() => { checkString(Buffer.from([i]).toString()) })
|
||||
}
|
||||
|
||||
// del is not allowed
|
||||
t.throws(() => { check('\u007f') })
|
||||
t.throws(() => { checkString('\u007f') })
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('check() rejects high code points', t => {
|
||||
test('checkString() rejects high code points', t => {
|
||||
// char codes 128-255 are not allowed
|
||||
for (let i = 0x80; i <= 0xff; i++) {
|
||||
t.throws(() => { check(Buffer.from([i]).toString()) })
|
||||
t.throws(() => { checkString(Buffer.from([i]).toString()) })
|
||||
}
|
||||
|
||||
// emojis are not allowed
|
||||
t.throws(() => { check('💩') })
|
||||
t.throws(() => { check('❤️') })
|
||||
t.throws(() => { check('✨') })
|
||||
t.throws(() => { checkString('💩') })
|
||||
t.throws(() => { checkString('❤️') })
|
||||
t.throws(() => { checkString('✨') })
|
||||
|
||||
// ansi escape sequences are not allowed
|
||||
t.throws(() => { check('\u001B') })
|
||||
t.throws(() => { check('\u001B[4mfoo\u001B[24m') })
|
||||
t.throws(() => { check('\u001B[31mfoo\u001B[39m') })
|
||||
t.throws(() => { check('\u001B[41mfoo\u001B[49m') })
|
||||
t.throws(() => { check('\u001B[31m\u001B[42m\u001B[4mfoo\u001B[24m\u001B[49m\u001B[39m') })
|
||||
t.throws(() => { check('\u001B[31mfoo\u001B[4m\u001B[44mbar\u001B[49m\u001B[24m!\u001B[39m') })
|
||||
t.throws(() => { check('\u001B[31ma\u001B[33mb\u001B[32mc\u001B[33mb\u001B[31mc\u001B[39m') })
|
||||
t.throws(() => { check('\u001B[90mhello\u001B[39m\n\u001B[90mworld\u001B[39m') })
|
||||
t.throws(() => { checkString('\u001B') })
|
||||
t.throws(() => { checkString('\u001B[4mfoo\u001B[24m') })
|
||||
t.throws(() => { checkString('\u001B[31mfoo\u001B[39m') })
|
||||
t.throws(() => { checkString('\u001B[41mfoo\u001B[49m') })
|
||||
t.throws(() => { checkString('\u001B[31m\u001B[42m\u001B[4mfoo\u001B[24m\u001B[49m\u001B[39m') })
|
||||
t.throws(() => { checkString('\u001B[31mfoo\u001B[4m\u001B[44mbar\u001B[49m\u001B[24m!\u001B[39m') })
|
||||
t.throws(() => { checkString('\u001B[31ma\u001B[33mb\u001B[32mc\u001B[33mb\u001B[31mc\u001B[39m') })
|
||||
t.throws(() => { checkString('\u001B[90mhello\u001B[39m\n\u001B[90mworld\u001B[39m') })
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('check() accepts valid strings', t => {
|
||||
test('checkString() accepts valid strings', t => {
|
||||
// 20 lines, with 20 line max
|
||||
t.doesNotThrow(() => {
|
||||
check('a\nb\nc\nd\ne\nf\ng\nh\ni\nj\na\nb\nc\nd\ne\nf\ng\nh\ni\nj', 20)
|
||||
checkString('a\nb\nc\nd\ne\nf\ng\nh\ni\nj\na\nb\nc\nd\ne\nf\ng\nh\ni\nj', 20)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('check() rejects invalid strings', t => {
|
||||
test('checkString() rejects invalid strings', t => {
|
||||
// 3 character line, followed by line with unsafe characters
|
||||
t.throws(() => {
|
||||
check('abc\ndef💩gih')
|
||||
checkString('abc\ndef💩gih')
|
||||
})
|
||||
|
||||
// two lines with invalid characters
|
||||
t.throws(() => {
|
||||
check('🌟\ndef💩gih')
|
||||
checkString('🌟\ndef💩gih')
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('check() rejects non-strings', t => {
|
||||
test('checkString() rejects non-strings', t => {
|
||||
// function argument
|
||||
t.throws(() => {
|
||||
check(() => {})
|
||||
checkString(() => {})
|
||||
})
|
||||
|
||||
// object argument
|
||||
t.throws(() => {
|
||||
check({})
|
||||
checkString({})
|
||||
})
|
||||
|
||||
// number argument
|
||||
t.throws(() => {
|
||||
check(42)
|
||||
checkString(42)
|
||||
})
|
||||
|
||||
// null argument
|
||||
t.throws(() => {
|
||||
check(null)
|
||||
checkString(null)
|
||||
})
|
||||
|
||||
// undefined argument
|
||||
t.throws(() => {
|
||||
check(undefined)
|
||||
checkString(undefined)
|
||||
})
|
||||
|
||||
t.end()
|
||||
|
|
101
test/funding.js
101
test/funding.js
|
@ -2,15 +2,114 @@ const test = require('tape')
|
|||
const cp = require('child_process')
|
||||
const path = require('path')
|
||||
|
||||
const { clearShown } = require('../lib/limit')
|
||||
|
||||
const FUNDING_BIN_PATH = path.join(__dirname, '..', 'bin', 'funding.js')
|
||||
|
||||
test('Santiy check bin/funding.js output', t => {
|
||||
test('Sanity check bin/funding.js output', t => {
|
||||
t.plan(4)
|
||||
|
||||
clearShown()
|
||||
|
||||
cp.execFile(FUNDING_BIN_PATH, (err, stdout, stderr) => {
|
||||
t.error(err)
|
||||
t.ok(stdout.length > 0, 'there exists some stdout ouput')
|
||||
t.ok(!stdout.match(/error/gi), 'stdout output is not an error')
|
||||
t.equal(stderr, '', 'no stderr output')
|
||||
})
|
||||
})
|
||||
|
||||
test('`npm --silent` or `npm --loglevel silent` prevents output', t => {
|
||||
t.plan(3)
|
||||
|
||||
clearShown()
|
||||
|
||||
const opts = {
|
||||
env: Object.assign({}, process.env, {
|
||||
npm_config_loglevel: 'silent'
|
||||
})
|
||||
}
|
||||
|
||||
cp.execFile(FUNDING_BIN_PATH, [], opts, (err, stdout, stderr) => {
|
||||
t.error(err)
|
||||
t.equal(stdout, '', 'no stdout ouput')
|
||||
t.equal(stderr, '', 'no stderr output')
|
||||
})
|
||||
})
|
||||
|
||||
test('`npm --quiet` or `npm --loglevel warn` prevents output', t => {
|
||||
if (process.version.startsWith('v6.')) {
|
||||
t.pass('Ignore `--loglevel warn` on Node 6 (it is the default)')
|
||||
return t.end()
|
||||
}
|
||||
|
||||
t.plan(3)
|
||||
|
||||
clearShown()
|
||||
|
||||
const opts = {
|
||||
env: Object.assign({}, process.env, {
|
||||
npm_config_loglevel: 'warn'
|
||||
})
|
||||
}
|
||||
|
||||
cp.execFile(FUNDING_BIN_PATH, [], opts, (err, stdout, stderr) => {
|
||||
t.error(err)
|
||||
t.equal(stdout, '', 'no stdout ouput')
|
||||
t.equal(stderr, '', 'no stderr output')
|
||||
})
|
||||
})
|
||||
|
||||
test('`npm --loglevel error` prevents output', t => {
|
||||
t.plan(3)
|
||||
|
||||
clearShown()
|
||||
|
||||
const opts = {
|
||||
env: Object.assign({}, process.env, {
|
||||
npm_config_loglevel: 'error'
|
||||
})
|
||||
}
|
||||
|
||||
cp.execFile(FUNDING_BIN_PATH, [], opts, (err, stdout, stderr) => {
|
||||
t.error(err)
|
||||
t.equal(stdout, '', 'no stdout ouput')
|
||||
t.equal(stderr, '', 'no stderr output')
|
||||
})
|
||||
})
|
||||
|
||||
test('deduplication / rate-limiting', t => {
|
||||
t.plan(7)
|
||||
|
||||
clearShown()
|
||||
|
||||
cp.execFile(FUNDING_BIN_PATH, (err, stdout, stderr) => {
|
||||
t.error(err)
|
||||
t.ok(stdout.length > 0, 'there exists some stdout ouput')
|
||||
t.ok(!stdout.match(/error/gi), 'stdout output is not an error')
|
||||
t.equal(stderr, '', 'no stderr output')
|
||||
|
||||
// Second run should print nothing, since it was recently shown
|
||||
cp.execFile(FUNDING_BIN_PATH, (err, stdout, stderr) => {
|
||||
t.error(err)
|
||||
t.equal(stdout, '', 'no stdout ouput')
|
||||
t.equal(stderr, '', 'no stderr output')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('OPEN_SOURCE_CONTRIBUTOR=true prevents output', t => {
|
||||
t.plan(3)
|
||||
|
||||
const opts = {
|
||||
env: Object.assign({}, process.env, {
|
||||
OPEN_SOURCE_CONTRIBUTOR: 'true'
|
||||
})
|
||||
}
|
||||
|
||||
cp.execFile(FUNDING_BIN_PATH, [], opts, (err, stdout, stderr) => {
|
||||
t.error(err)
|
||||
t.equal(stdout.length, 0, 'no stdout ouput')
|
||||
t.equal(stderr.length, 0, 'no stderr output')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
const test = require('tape')
|
||||
|
||||
const { isShownRecently, markShown, clearShown } = require('../lib/limit')
|
||||
|
||||
test('shown file works', t => {
|
||||
clearShown()
|
||||
t.ok(!isShownRecently(), 'initially, not shown recently')
|
||||
|
||||
markShown()
|
||||
t.ok(isShownRecently(), 'after markShown(), is shown recently')
|
||||
|
||||
clearShown()
|
||||
t.ok(!isShownRecently(), 'after clearShown(), not shown recently')
|
||||
|
||||
t.end()
|
||||
})
|
|
@ -1,6 +1,6 @@
|
|||
const test = require('tape')
|
||||
const check = require('../lib/check')
|
||||
const funding = require('../')
|
||||
|
||||
const { checkString, checkMessage } = require('../lib/check')
|
||||
const messages = require('../messages.json')
|
||||
|
||||
test('Messages is in the expected shape', t => {
|
||||
|
@ -17,26 +17,20 @@ test('Check all messages with check()', t => {
|
|||
t.equal(typeof message.url, 'string')
|
||||
|
||||
t.doesNotThrow(() => {
|
||||
check(message.title)
|
||||
})
|
||||
checkString(message.title)
|
||||
}, 'checkString(message.title)')
|
||||
|
||||
t.doesNotThrow(() => {
|
||||
check(message.text)
|
||||
})
|
||||
checkString(message.text)
|
||||
}, 'checkString(message.text)')
|
||||
|
||||
t.doesNotThrow(() => {
|
||||
check(message.url)
|
||||
})
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('Check all messages with checkMessage()', t => {
|
||||
messages.forEach(message => {
|
||||
t.doesNotThrow(() => {
|
||||
funding.checkMessage(message)
|
||||
})
|
||||
checkString(message.url)
|
||||
}, 'checkString(message.url)')
|
||||
|
||||
t.doesNotThrow(() => {
|
||||
checkMessage(message)
|
||||
}, 'checkMessage(message)')
|
||||
})
|
||||
|
||||
t.end()
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const { clearShown } = require('../lib/limit')
|
||||
|
||||
clearShown()
|
Loading…
Reference in New Issue