mirror of https://github.com/feross/funding
Compare commits
No commits in common. "master" and "v1.0.2" have entirely different histories.
|
@ -1,3 +1,2 @@
|
||||||
.travis.yml
|
.travis.yml
|
||||||
test/
|
test/
|
||||||
tools/
|
|
||||||
|
|
20
README.md
20
README.md
|
@ -11,29 +11,25 @@
|
||||||
[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.
|
||||||
|
|
||||||
## Install
|
## Usage
|
||||||
|
|
||||||
```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. 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. 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.
|
||||||
|
|
||||||
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 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!
|
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!
|
||||||
|
|
||||||
## Where is this experiment running?
|
## Where is this experiment running?
|
||||||
|
|
||||||
|
@ -41,8 +37,6 @@ 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!
|
||||||
|
@ -68,11 +62,3 @@ 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.printMessage()
|
funding.printRandomMessage()
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err.stack || err.message || err)
|
console.error(err.stack || err.message || err)
|
||||||
|
|
82
index.js
82
index.js
|
@ -1,27 +1,20 @@
|
||||||
const boxen = require('boxen')
|
const boxen = require('boxen')
|
||||||
const chalk = require('chalk')
|
const chalk = require('chalk')
|
||||||
|
|
||||||
const {
|
const detect = require('./lib/detect')
|
||||||
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')
|
const wrap = require('./lib/wrap')
|
||||||
|
const check = require('./lib/check')
|
||||||
|
|
||||||
|
const messages = require('./messages.json')
|
||||||
|
|
||||||
function formatTitle (title) {
|
function formatTitle (title) {
|
||||||
title = wrap(title)
|
title = wrap(title)
|
||||||
|
|
||||||
if (!isCI()) {
|
if (!detect.isTravis()) {
|
||||||
title = chalk.black(title)
|
title = chalk.black(title)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isHyper() && !isITerm()) {
|
if (!detect.isITerm() && !detect.isHyper()) {
|
||||||
title = chalk.bold(title)
|
title = chalk.bold(title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +29,7 @@ function formatText (text) {
|
||||||
(match, url) => chalk.blue.underline(url)
|
(match, url) => chalk.blue.underline(url)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!isCI()) {
|
if (!detect.isTravis()) {
|
||||||
text = chalk.black(text)
|
text = chalk.black(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,16 +49,7 @@ function formatMessage (message) {
|
||||||
|
|
||||||
const opts = {
|
const opts = {
|
||||||
align: 'center',
|
align: 'center',
|
||||||
borderStyle: {
|
|
||||||
topLeft: ' ',
|
|
||||||
topRight: ' ',
|
|
||||||
bottomLeft: ' ',
|
|
||||||
bottomRight: ' ',
|
|
||||||
horizontal: ' ',
|
|
||||||
vertical: ' '
|
|
||||||
},
|
|
||||||
float: 'center',
|
float: 'center',
|
||||||
margin: 0,
|
|
||||||
padding: {
|
padding: {
|
||||||
top: 1,
|
top: 1,
|
||||||
right: 4,
|
right: 4,
|
||||||
|
@ -74,28 +58,32 @@ function formatMessage (message) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isCI()) {
|
if (detect.isTravis()) {
|
||||||
Object.assign(opts, {
|
Object.assign(opts, {
|
||||||
backgroundColor: 'white'
|
borderColor: 'green',
|
||||||
|
borderStyle: 'bold',
|
||||||
|
margin: 1
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Object.assign(opts, {
|
||||||
|
borderStyle: {
|
||||||
|
topLeft: ' ',
|
||||||
|
topRight: ' ',
|
||||||
|
bottomLeft: ' ',
|
||||||
|
bottomRight: ' ',
|
||||||
|
horizontal: ' ',
|
||||||
|
vertical: ' '
|
||||||
|
},
|
||||||
|
backgroundColor: 'white',
|
||||||
|
margin: 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return boxen(coloredMessage, opts)
|
return boxen(coloredMessage, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
function printMessage () {
|
function checkMessage (message) {
|
||||||
// Do not print message when npm is run in silent mode
|
const { title, text, url } = message
|
||||||
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
|
||||||
|
@ -104,16 +92,22 @@ function printMessage () {
|
||||||
// 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.
|
||||||
checkMessage(message)
|
check(title)
|
||||||
|
check(text)
|
||||||
|
check(url)
|
||||||
|
}
|
||||||
|
|
||||||
// Format the message and print it
|
function printRandomMessage () {
|
||||||
|
const i = Math.floor(Math.random() * messages.length)
|
||||||
|
const message = messages[i]
|
||||||
|
|
||||||
|
checkMessage(message)
|
||||||
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 = {
|
||||||
printMessage
|
formatMessage,
|
||||||
|
checkMessage,
|
||||||
|
printRandomMessage
|
||||||
}
|
}
|
||||||
|
|
13
lib/check.js
13
lib/check.js
|
@ -18,15 +18,4 @@ function checkString (str) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkMessage (message) {
|
module.exports = checkString
|
||||||
const { title, text, url } = message
|
|
||||||
|
|
||||||
checkString(title)
|
|
||||||
checkString(text)
|
|
||||||
checkString(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
checkString,
|
|
||||||
checkMessage
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
/**
|
/**
|
||||||
* Functions to detect information about the environment, e.g. which Terminal
|
* Functions to detect which Terminal emulator is in use.
|
||||||
* emulator is in use, or whether silent mode is enabled.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const ciInfo = require('ci-info')
|
const ciInfo = require('ci-info')
|
||||||
|
|
||||||
const {
|
const TERM_PROGRAM = process.env.TERM_PROGRAM
|
||||||
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'
|
||||||
|
@ -20,24 +15,12 @@ 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 CI?
|
// Is Travis (CI)?
|
||||||
const isCI = () => ciInfo.isCI
|
const isTravis = () => ciInfo.TRAVIS
|
||||||
|
|
||||||
// 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,
|
||||||
isCI,
|
isTravis
|
||||||
isSilentMode
|
|
||||||
}
|
}
|
||||||
|
|
37
lib/limit.js
37
lib/limit.js
|
@ -1,37 +0,0 @@
|
||||||
/**
|
|
||||||
* 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,7 +1,12 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"title": "npm install funding",
|
"title": "Linode cloud computing",
|
||||||
"text": "I appreciate the thoughtful discussion and feedback from the community. I shared some thoughts about how this experiment went from my perspective.",
|
"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://feross.org/funding-experiment-recap/"
|
"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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "funding",
|
"name": "funding",
|
||||||
"description": "Get open source maintainers paid",
|
"description": "Get open source maintainers paid",
|
||||||
"version": "1.0.9",
|
"version": "1.0.2",
|
||||||
"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": "^5.0.0"
|
"tape": "^4.11.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 tools/clear.js && node bin/funding.js",
|
"start": "node bin/funding.js",
|
||||||
"test": "standard && npm start && tape test/*.js"
|
"test": "standard && node bin/funding.js && tape test/*.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
132
test/check.js
132
test/check.js
|
@ -1,146 +1,146 @@
|
||||||
const test = require('tape')
|
const test = require('tape')
|
||||||
const { checkString } = require('../lib/check')
|
const check = require('../lib/check')
|
||||||
|
|
||||||
test('checkString() accepts valid strings', t => {
|
test('check() accepts valid strings', t => {
|
||||||
t.doesNotThrow(() => {
|
t.doesNotThrow(() => {
|
||||||
checkString('')
|
check('')
|
||||||
})
|
})
|
||||||
t.doesNotThrow(() => {
|
t.doesNotThrow(() => {
|
||||||
checkString('support open source')
|
check('support open source')
|
||||||
})
|
})
|
||||||
t.doesNotThrow(() => {
|
t.doesNotThrow(() => {
|
||||||
checkString('support open source\nbe a part of history')
|
check('support open source\nbe a part of history')
|
||||||
})
|
})
|
||||||
t.doesNotThrow(() => {
|
t.doesNotThrow(() => {
|
||||||
checkString('support open source\nbe a part of history\nmaintainers unite')
|
check('support open source\nbe a part of history\nmaintainers unite')
|
||||||
})
|
})
|
||||||
t.doesNotThrow(() => {
|
t.doesNotThrow(() => {
|
||||||
checkString('!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
|
check('!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
|
||||||
})
|
})
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('checkString() behaves as expected on first 127 characters', t => {
|
test('check() behaves as expected on first 127 characters', t => {
|
||||||
// control characters not allowed
|
// control characters not allowed
|
||||||
t.throws(() => { checkString('\u0000') })
|
t.throws(() => { check('\u0000') })
|
||||||
t.throws(() => { checkString('\u0001') })
|
t.throws(() => { check('\u0001') })
|
||||||
t.throws(() => { checkString('\u0002') })
|
t.throws(() => { check('\u0002') })
|
||||||
t.throws(() => { checkString('\u0003') })
|
t.throws(() => { check('\u0003') })
|
||||||
t.throws(() => { checkString('\u0004') })
|
t.throws(() => { check('\u0004') })
|
||||||
t.throws(() => { checkString('\u0005') })
|
t.throws(() => { check('\u0005') })
|
||||||
t.throws(() => { checkString('\u0006') })
|
t.throws(() => { check('\u0006') })
|
||||||
t.throws(() => { checkString('\u0007') })
|
t.throws(() => { check('\u0007') })
|
||||||
t.throws(() => { checkString('\u0008') })
|
t.throws(() => { check('\u0008') })
|
||||||
t.throws(() => { checkString('\u0009') })
|
t.throws(() => { check('\u0009') })
|
||||||
|
|
||||||
// newline is allowed
|
// newline is allowed
|
||||||
t.doesNotThrow(() => { checkString('\u000a') })
|
t.doesNotThrow(() => { check('\u000a') })
|
||||||
|
|
||||||
// control characters not allowed
|
// control characters not allowed
|
||||||
t.throws(() => { checkString('\u000b') })
|
t.throws(() => { check('\u000b') })
|
||||||
t.throws(() => { checkString('\u000c') })
|
t.throws(() => { check('\u000c') })
|
||||||
t.throws(() => { checkString('\u000d') })
|
t.throws(() => { check('\u000d') })
|
||||||
t.throws(() => { checkString('\u000e') })
|
t.throws(() => { check('\u000e') })
|
||||||
t.throws(() => { checkString('\u000f') })
|
t.throws(() => { check('\u000f') })
|
||||||
t.throws(() => { checkString('\u0010') })
|
t.throws(() => { check('\u0010') })
|
||||||
t.throws(() => { checkString('\u0011') })
|
t.throws(() => { check('\u0011') })
|
||||||
t.throws(() => { checkString('\u0012') })
|
t.throws(() => { check('\u0012') })
|
||||||
t.throws(() => { checkString('\u0013') })
|
t.throws(() => { check('\u0013') })
|
||||||
t.throws(() => { checkString('\u0014') })
|
t.throws(() => { check('\u0014') })
|
||||||
t.throws(() => { checkString('\u0015') })
|
t.throws(() => { check('\u0015') })
|
||||||
t.throws(() => { checkString('\u0016') })
|
t.throws(() => { check('\u0016') })
|
||||||
t.throws(() => { checkString('\u0017') })
|
t.throws(() => { check('\u0017') })
|
||||||
t.throws(() => { checkString('\u0018') })
|
t.throws(() => { check('\u0018') })
|
||||||
t.throws(() => { checkString('\u0019') })
|
t.throws(() => { check('\u0019') })
|
||||||
t.throws(() => { checkString('\u001a') })
|
t.throws(() => { check('\u001a') })
|
||||||
t.throws(() => { checkString('\u001b') })
|
t.throws(() => { check('\u001b') })
|
||||||
t.throws(() => { checkString('\u001c') })
|
t.throws(() => { check('\u001c') })
|
||||||
t.throws(() => { checkString('\u001d') })
|
t.throws(() => { check('\u001d') })
|
||||||
t.throws(() => { checkString('\u001e') })
|
t.throws(() => { check('\u001e') })
|
||||||
t.throws(() => { checkString('\u001f') })
|
t.throws(() => { check('\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(() => { checkString(Buffer.from([i]).toString()) })
|
t.doesNotThrow(() => { check(Buffer.from([i]).toString()) })
|
||||||
}
|
}
|
||||||
|
|
||||||
// del is not allowed
|
// del is not allowed
|
||||||
t.throws(() => { checkString('\u007f') })
|
t.throws(() => { check('\u007f') })
|
||||||
|
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('checkString() rejects high code points', t => {
|
test('check() 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(() => { checkString(Buffer.from([i]).toString()) })
|
t.throws(() => { check(Buffer.from([i]).toString()) })
|
||||||
}
|
}
|
||||||
|
|
||||||
// emojis are not allowed
|
// emojis are not allowed
|
||||||
t.throws(() => { checkString('💩') })
|
t.throws(() => { check('💩') })
|
||||||
t.throws(() => { checkString('❤️') })
|
t.throws(() => { check('❤️') })
|
||||||
t.throws(() => { checkString('✨') })
|
t.throws(() => { check('✨') })
|
||||||
|
|
||||||
// ansi escape sequences are not allowed
|
// ansi escape sequences are not allowed
|
||||||
t.throws(() => { checkString('\u001B') })
|
t.throws(() => { check('\u001B') })
|
||||||
t.throws(() => { checkString('\u001B[4mfoo\u001B[24m') })
|
t.throws(() => { check('\u001B[4mfoo\u001B[24m') })
|
||||||
t.throws(() => { checkString('\u001B[31mfoo\u001B[39m') })
|
t.throws(() => { check('\u001B[31mfoo\u001B[39m') })
|
||||||
t.throws(() => { checkString('\u001B[41mfoo\u001B[49m') })
|
t.throws(() => { check('\u001B[41mfoo\u001B[49m') })
|
||||||
t.throws(() => { checkString('\u001B[31m\u001B[42m\u001B[4mfoo\u001B[24m\u001B[49m\u001B[39m') })
|
t.throws(() => { check('\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(() => { check('\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(() => { check('\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.throws(() => { check('\u001B[90mhello\u001B[39m\n\u001B[90mworld\u001B[39m') })
|
||||||
|
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('checkString() accepts valid strings', t => {
|
test('check() accepts valid strings', t => {
|
||||||
// 20 lines, with 20 line max
|
// 20 lines, with 20 line max
|
||||||
t.doesNotThrow(() => {
|
t.doesNotThrow(() => {
|
||||||
checkString('a\nb\nc\nd\ne\nf\ng\nh\ni\nj\na\nb\nc\nd\ne\nf\ng\nh\ni\nj', 20)
|
check('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('checkString() rejects invalid strings', t => {
|
test('check() 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(() => {
|
||||||
checkString('abc\ndef💩gih')
|
check('abc\ndef💩gih')
|
||||||
})
|
})
|
||||||
|
|
||||||
// two lines with invalid characters
|
// two lines with invalid characters
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
checkString('🌟\ndef💩gih')
|
check('🌟\ndef💩gih')
|
||||||
})
|
})
|
||||||
|
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('checkString() rejects non-strings', t => {
|
test('check() rejects non-strings', t => {
|
||||||
// function argument
|
// function argument
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
checkString(() => {})
|
check(() => {})
|
||||||
})
|
})
|
||||||
|
|
||||||
// object argument
|
// object argument
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
checkString({})
|
check({})
|
||||||
})
|
})
|
||||||
|
|
||||||
// number argument
|
// number argument
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
checkString(42)
|
check(42)
|
||||||
})
|
})
|
||||||
|
|
||||||
// null argument
|
// null argument
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
checkString(null)
|
check(null)
|
||||||
})
|
})
|
||||||
|
|
||||||
// undefined argument
|
// undefined argument
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
checkString(undefined)
|
check(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.end()
|
t.end()
|
||||||
|
|
101
test/funding.js
101
test/funding.js
|
@ -2,114 +2,15 @@ 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('Sanity check bin/funding.js output', t => {
|
test('Santiy 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')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
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 { checkString, checkMessage } = require('../lib/check')
|
const funding = require('../')
|
||||||
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,20 +17,26 @@ test('Check all messages with check()', t => {
|
||||||
t.equal(typeof message.url, 'string')
|
t.equal(typeof message.url, 'string')
|
||||||
|
|
||||||
t.doesNotThrow(() => {
|
t.doesNotThrow(() => {
|
||||||
checkString(message.title)
|
check(message.title)
|
||||||
}, 'checkString(message.title)')
|
})
|
||||||
|
|
||||||
t.doesNotThrow(() => {
|
t.doesNotThrow(() => {
|
||||||
checkString(message.text)
|
check(message.text)
|
||||||
}, 'checkString(message.text)')
|
})
|
||||||
|
|
||||||
t.doesNotThrow(() => {
|
t.doesNotThrow(() => {
|
||||||
checkString(message.url)
|
check(message.url)
|
||||||
}, 'checkString(message.url)')
|
})
|
||||||
|
})
|
||||||
t.doesNotThrow(() => {
|
|
||||||
checkMessage(message)
|
t.end()
|
||||||
}, 'checkMessage(message)')
|
})
|
||||||
|
|
||||||
|
test('Check all messages with checkMessage()', t => {
|
||||||
|
messages.forEach(message => {
|
||||||
|
t.doesNotThrow(() => {
|
||||||
|
funding.checkMessage(message)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.end()
|
t.end()
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const { clearShown } = require('../lib/limit')
|
|
||||||
|
|
||||||
clearShown()
|
|
Loading…
Reference in New Issue