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
|
.travis.yml
|
||||||
test/
|
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-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg
|
||||||
[standard-url]: https://standardjs.com
|
[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.
|
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
|
```bash
|
||||||
npm install funding
|
npm install funding
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### UPDATE: The experiment is over – Feross posted [a recap](https://feross.org/funding-experiment-recap/) on his blog
|
||||||
|
|
||||||
## What is this?
|
## What is this?
|
||||||
|
|
||||||
This is an open source funding experiment! ✨
|
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.
|
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?
|
## 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?
|
## 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)
|
- [`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?
|
## Who is Feross?
|
||||||
|
|
||||||
Hey there, I'm 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?
|
## Where can I provide feedback about this experiment?
|
||||||
|
|
||||||
You can open an issue. But please be kind. I'm a human with feelings. ❤️
|
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 {
|
try {
|
||||||
if (funding) {
|
if (funding) {
|
||||||
funding.printRandomMessage()
|
funding.printMessage()
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err.stack || err.message || err)
|
console.error(err.stack || err.message || err)
|
||||||
|
|
84
index.js
84
index.js
|
@ -1,20 +1,27 @@
|
||||||
const boxen = require('boxen')
|
const boxen = require('boxen')
|
||||||
const chalk = require('chalk')
|
const chalk = require('chalk')
|
||||||
|
|
||||||
const detect = require('./lib/detect')
|
const {
|
||||||
const wrap = require('./lib/wrap')
|
isHyper,
|
||||||
const check = require('./lib/check')
|
isITerm,
|
||||||
|
isCI,
|
||||||
|
isSilentMode
|
||||||
|
} = require('./lib/detect')
|
||||||
|
|
||||||
|
const { isShownRecently, markShown } = require('./lib/limit')
|
||||||
|
|
||||||
|
const { checkMessage } = require('./lib/check')
|
||||||
const messages = require('./messages.json')
|
const messages = require('./messages.json')
|
||||||
|
const wrap = require('./lib/wrap')
|
||||||
|
|
||||||
function formatTitle (title) {
|
function formatTitle (title) {
|
||||||
title = wrap(title)
|
title = wrap(title)
|
||||||
|
|
||||||
if (!detect.isTravis()) {
|
if (!isCI()) {
|
||||||
title = chalk.black(title)
|
title = chalk.black(title)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!detect.isITerm() && !detect.isHyper()) {
|
if (!isHyper() && !isITerm()) {
|
||||||
title = chalk.bold(title)
|
title = chalk.bold(title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +36,7 @@ function formatText (text) {
|
||||||
(match, url) => chalk.blue.underline(url)
|
(match, url) => chalk.blue.underline(url)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!detect.isTravis()) {
|
if (!isCI()) {
|
||||||
text = chalk.black(text)
|
text = chalk.black(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,23 +56,6 @@ function formatMessage (message) {
|
||||||
|
|
||||||
const opts = {
|
const opts = {
|
||||||
align: 'center',
|
align: 'center',
|
||||||
float: 'center',
|
|
||||||
padding: {
|
|
||||||
top: 1,
|
|
||||||
right: 4,
|
|
||||||
bottom: 1,
|
|
||||||
left: 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (detect.isTravis()) {
|
|
||||||
Object.assign(opts, {
|
|
||||||
borderColor: 'green',
|
|
||||||
borderStyle: 'bold',
|
|
||||||
margin: 1
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Object.assign(opts, {
|
|
||||||
borderStyle: {
|
borderStyle: {
|
||||||
topLeft: ' ',
|
topLeft: ' ',
|
||||||
topRight: ' ',
|
topRight: ' ',
|
||||||
|
@ -74,16 +64,38 @@ function formatMessage (message) {
|
||||||
horizontal: ' ',
|
horizontal: ' ',
|
||||||
vertical: ' '
|
vertical: ' '
|
||||||
},
|
},
|
||||||
backgroundColor: 'white',
|
float: 'center',
|
||||||
margin: 0
|
margin: 0,
|
||||||
|
padding: {
|
||||||
|
top: 1,
|
||||||
|
right: 4,
|
||||||
|
bottom: 1,
|
||||||
|
left: 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCI()) {
|
||||||
|
Object.assign(opts, {
|
||||||
|
backgroundColor: 'white'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return boxen(coloredMessage, opts)
|
return boxen(coloredMessage, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkMessage (message) {
|
function printMessage () {
|
||||||
const { title, text, url } = message
|
// 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
|
// Check if the strings are safe to print to the terminal. Specifically, the
|
||||||
// string should be plain ASCII, excluding control characters. This is
|
// 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
|
// 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
|
// to check again in the client and assert that messages are plain ASCII. This
|
||||||
// is the security principle of defense-in-depth.
|
// 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)
|
checkMessage(message)
|
||||||
|
|
||||||
|
// Format the message and print it
|
||||||
const formattedMessage = formatMessage(message)
|
const formattedMessage = formatMessage(message)
|
||||||
console.log(formattedMessage + '\n')
|
console.log(formattedMessage + '\n')
|
||||||
|
|
||||||
|
// Limit the frequency that messages are shown
|
||||||
|
markShown()
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
formatMessage,
|
printMessage
|
||||||
checkMessage,
|
|
||||||
printRandomMessage
|
|
||||||
}
|
}
|
||||||
|
|
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 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)?
|
// Is Hyper (Mac)?
|
||||||
const isHyper = () => TERM_PROGRAM === 'Hyper'
|
const isHyper = () => TERM_PROGRAM === 'Hyper'
|
||||||
|
@ -15,12 +20,24 @@ const isITerm = () => TERM_PROGRAM === 'iTerm.app'
|
||||||
// Is Terminal.app (Mac)?
|
// Is Terminal.app (Mac)?
|
||||||
const isTerminalApp = () => TERM_PROGRAM === 'Apple_Terminal'
|
const isTerminalApp = () => TERM_PROGRAM === 'Apple_Terminal'
|
||||||
|
|
||||||
// Is Travis (CI)?
|
// Is CI?
|
||||||
const isTravis = () => ciInfo.TRAVIS
|
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 = {
|
module.exports = {
|
||||||
isHyper,
|
isHyper,
|
||||||
isITerm,
|
isITerm,
|
||||||
isTerminalApp,
|
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",
|
"title": "npm install funding",
|
||||||
"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.",
|
"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://welcome.linode.com/standardjs"
|
"url": "https://feross.org/funding-experiment-recap/"
|
||||||
},
|
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "funding",
|
"name": "funding",
|
||||||
"description": "Get open source maintainers paid",
|
"description": "Get open source maintainers paid",
|
||||||
"version": "1.0.2",
|
"version": "1.0.9",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Feross Aboukhadijeh",
|
"name": "Feross Aboukhadijeh",
|
||||||
"email": "feross@feross.org",
|
"email": "feross@feross.org",
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"standard": "*",
|
"standard": "*",
|
||||||
"tape": "^4.11.0"
|
"tape": "^5.0.0"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/feross/funding",
|
"homepage": "https://github.com/feross/funding",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "node bin/funding.js",
|
"postinstall": "node bin/funding.js",
|
||||||
"start": "node bin/funding.js",
|
"start": "node tools/clear.js && node bin/funding.js",
|
||||||
"test": "standard && node bin/funding.js && tape test/*.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 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(() => {
|
t.doesNotThrow(() => {
|
||||||
check('')
|
checkString('')
|
||||||
})
|
})
|
||||||
t.doesNotThrow(() => {
|
t.doesNotThrow(() => {
|
||||||
check('support open source')
|
checkString('support open source')
|
||||||
})
|
})
|
||||||
t.doesNotThrow(() => {
|
t.doesNotThrow(() => {
|
||||||
check('support open source\nbe a part of history')
|
checkString('support open source\nbe a part of history')
|
||||||
})
|
})
|
||||||
t.doesNotThrow(() => {
|
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(() => {
|
t.doesNotThrow(() => {
|
||||||
check('!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
|
checkString('!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
|
||||||
})
|
})
|
||||||
t.end()
|
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
|
// control characters not allowed
|
||||||
t.throws(() => { check('\u0000') })
|
t.throws(() => { checkString('\u0000') })
|
||||||
t.throws(() => { check('\u0001') })
|
t.throws(() => { checkString('\u0001') })
|
||||||
t.throws(() => { check('\u0002') })
|
t.throws(() => { checkString('\u0002') })
|
||||||
t.throws(() => { check('\u0003') })
|
t.throws(() => { checkString('\u0003') })
|
||||||
t.throws(() => { check('\u0004') })
|
t.throws(() => { checkString('\u0004') })
|
||||||
t.throws(() => { check('\u0005') })
|
t.throws(() => { checkString('\u0005') })
|
||||||
t.throws(() => { check('\u0006') })
|
t.throws(() => { checkString('\u0006') })
|
||||||
t.throws(() => { check('\u0007') })
|
t.throws(() => { checkString('\u0007') })
|
||||||
t.throws(() => { check('\u0008') })
|
t.throws(() => { checkString('\u0008') })
|
||||||
t.throws(() => { check('\u0009') })
|
t.throws(() => { checkString('\u0009') })
|
||||||
|
|
||||||
// newline is allowed
|
// newline is allowed
|
||||||
t.doesNotThrow(() => { check('\u000a') })
|
t.doesNotThrow(() => { checkString('\u000a') })
|
||||||
|
|
||||||
// control characters not allowed
|
// control characters not allowed
|
||||||
t.throws(() => { check('\u000b') })
|
t.throws(() => { checkString('\u000b') })
|
||||||
t.throws(() => { check('\u000c') })
|
t.throws(() => { checkString('\u000c') })
|
||||||
t.throws(() => { check('\u000d') })
|
t.throws(() => { checkString('\u000d') })
|
||||||
t.throws(() => { check('\u000e') })
|
t.throws(() => { checkString('\u000e') })
|
||||||
t.throws(() => { check('\u000f') })
|
t.throws(() => { checkString('\u000f') })
|
||||||
t.throws(() => { check('\u0010') })
|
t.throws(() => { checkString('\u0010') })
|
||||||
t.throws(() => { check('\u0011') })
|
t.throws(() => { checkString('\u0011') })
|
||||||
t.throws(() => { check('\u0012') })
|
t.throws(() => { checkString('\u0012') })
|
||||||
t.throws(() => { check('\u0013') })
|
t.throws(() => { checkString('\u0013') })
|
||||||
t.throws(() => { check('\u0014') })
|
t.throws(() => { checkString('\u0014') })
|
||||||
t.throws(() => { check('\u0015') })
|
t.throws(() => { checkString('\u0015') })
|
||||||
t.throws(() => { check('\u0016') })
|
t.throws(() => { checkString('\u0016') })
|
||||||
t.throws(() => { check('\u0017') })
|
t.throws(() => { checkString('\u0017') })
|
||||||
t.throws(() => { check('\u0018') })
|
t.throws(() => { checkString('\u0018') })
|
||||||
t.throws(() => { check('\u0019') })
|
t.throws(() => { checkString('\u0019') })
|
||||||
t.throws(() => { check('\u001a') })
|
t.throws(() => { checkString('\u001a') })
|
||||||
t.throws(() => { check('\u001b') })
|
t.throws(() => { checkString('\u001b') })
|
||||||
t.throws(() => { check('\u001c') })
|
t.throws(() => { checkString('\u001c') })
|
||||||
t.throws(() => { check('\u001d') })
|
t.throws(() => { checkString('\u001d') })
|
||||||
t.throws(() => { check('\u001e') })
|
t.throws(() => { checkString('\u001e') })
|
||||||
t.throws(() => { check('\u001f') })
|
t.throws(() => { checkString('\u001f') })
|
||||||
|
|
||||||
// normal characters are allowed
|
// normal characters are allowed
|
||||||
for (let i = 0x20; i < 0x7f; i++) {
|
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
|
// del is not allowed
|
||||||
t.throws(() => { check('\u007f') })
|
t.throws(() => { checkString('\u007f') })
|
||||||
|
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('check() rejects high code points', t => {
|
test('checkString() rejects high code points', t => {
|
||||||
// char codes 128-255 are not allowed
|
// char codes 128-255 are not allowed
|
||||||
for (let i = 0x80; i <= 0xff; i++) {
|
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
|
// emojis are not allowed
|
||||||
t.throws(() => { check('💩') })
|
t.throws(() => { checkString('💩') })
|
||||||
t.throws(() => { check('❤️') })
|
t.throws(() => { checkString('❤️') })
|
||||||
t.throws(() => { check('✨') })
|
t.throws(() => { checkString('✨') })
|
||||||
|
|
||||||
// ansi escape sequences are not allowed
|
// ansi escape sequences are not allowed
|
||||||
t.throws(() => { check('\u001B') })
|
t.throws(() => { checkString('\u001B') })
|
||||||
t.throws(() => { check('\u001B[4mfoo\u001B[24m') })
|
t.throws(() => { checkString('\u001B[4mfoo\u001B[24m') })
|
||||||
t.throws(() => { check('\u001B[31mfoo\u001B[39m') })
|
t.throws(() => { checkString('\u001B[31mfoo\u001B[39m') })
|
||||||
t.throws(() => { check('\u001B[41mfoo\u001B[49m') })
|
t.throws(() => { checkString('\u001B[41mfoo\u001B[49m') })
|
||||||
t.throws(() => { check('\u001B[31m\u001B[42m\u001B[4mfoo\u001B[24m\u001B[49m\u001B[39m') })
|
t.throws(() => { checkString('\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(() => { checkString('\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(() => { checkString('\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[90mhello\u001B[39m\n\u001B[90mworld\u001B[39m') })
|
||||||
|
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('check() accepts valid strings', t => {
|
test('checkString() accepts valid strings', t => {
|
||||||
// 20 lines, with 20 line max
|
// 20 lines, with 20 line max
|
||||||
t.doesNotThrow(() => {
|
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()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('check() rejects invalid strings', t => {
|
test('checkString() rejects invalid strings', t => {
|
||||||
// 3 character line, followed by line with unsafe characters
|
// 3 character line, followed by line with unsafe characters
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
check('abc\ndef💩gih')
|
checkString('abc\ndef💩gih')
|
||||||
})
|
})
|
||||||
|
|
||||||
// two lines with invalid characters
|
// two lines with invalid characters
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
check('🌟\ndef💩gih')
|
checkString('🌟\ndef💩gih')
|
||||||
})
|
})
|
||||||
|
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('check() rejects non-strings', t => {
|
test('checkString() rejects non-strings', t => {
|
||||||
// function argument
|
// function argument
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
check(() => {})
|
checkString(() => {})
|
||||||
})
|
})
|
||||||
|
|
||||||
// object argument
|
// object argument
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
check({})
|
checkString({})
|
||||||
})
|
})
|
||||||
|
|
||||||
// number argument
|
// number argument
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
check(42)
|
checkString(42)
|
||||||
})
|
})
|
||||||
|
|
||||||
// null argument
|
// null argument
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
check(null)
|
checkString(null)
|
||||||
})
|
})
|
||||||
|
|
||||||
// undefined argument
|
// undefined argument
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
check(undefined)
|
checkString(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.end()
|
t.end()
|
||||||
|
|
101
test/funding.js
101
test/funding.js
|
@ -2,15 +2,114 @@ const test = require('tape')
|
||||||
const cp = require('child_process')
|
const cp = require('child_process')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
|
const { clearShown } = require('../lib/limit')
|
||||||
|
|
||||||
const FUNDING_BIN_PATH = path.join(__dirname, '..', 'bin', 'funding.js')
|
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)
|
t.plan(4)
|
||||||
|
|
||||||
|
clearShown()
|
||||||
|
|
||||||
cp.execFile(FUNDING_BIN_PATH, (err, stdout, stderr) => {
|
cp.execFile(FUNDING_BIN_PATH, (err, stdout, stderr) => {
|
||||||
t.error(err)
|
t.error(err)
|
||||||
t.ok(stdout.length > 0, 'there exists some stdout ouput')
|
t.ok(stdout.length > 0, 'there exists some stdout ouput')
|
||||||
t.ok(!stdout.match(/error/gi), 'stdout output is not an error')
|
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')
|
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 test = require('tape')
|
||||||
const check = require('../lib/check')
|
|
||||||
const funding = require('../')
|
const { checkString, checkMessage } = require('../lib/check')
|
||||||
const messages = require('../messages.json')
|
const messages = require('../messages.json')
|
||||||
|
|
||||||
test('Messages is in the expected shape', t => {
|
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.equal(typeof message.url, 'string')
|
||||||
|
|
||||||
t.doesNotThrow(() => {
|
t.doesNotThrow(() => {
|
||||||
check(message.title)
|
checkString(message.title)
|
||||||
})
|
}, 'checkString(message.title)')
|
||||||
|
|
||||||
t.doesNotThrow(() => {
|
t.doesNotThrow(() => {
|
||||||
check(message.text)
|
checkString(message.text)
|
||||||
})
|
}, 'checkString(message.text)')
|
||||||
|
|
||||||
t.doesNotThrow(() => {
|
t.doesNotThrow(() => {
|
||||||
check(message.url)
|
checkString(message.url)
|
||||||
})
|
}, 'checkString(message.url)')
|
||||||
})
|
|
||||||
|
t.doesNotThrow(() => {
|
||||||
t.end()
|
checkMessage(message)
|
||||||
})
|
}, 'checkMessage(message)')
|
||||||
|
|
||||||
test('Check all messages with checkMessage()', t => {
|
|
||||||
messages.forEach(message => {
|
|
||||||
t.doesNotThrow(() => {
|
|
||||||
funding.checkMessage(message)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.end()
|
t.end()
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const { clearShown } = require('../lib/limit')
|
||||||
|
|
||||||
|
clearShown()
|
Loading…
Reference in New Issue