Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add package.json "sustainability" property #187

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions doc/files/package.json.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,23 @@ Both email and url are optional either way.

npm also sets a top-level "maintainers" field with your npm user info.

## sustainability

You can specify an HTTP endpoint for up-to-date information about ways
to sustain development of your package so that people can learn more ways
to support your work:

{ "sustainability": "https://example.com/sustainability.json" }

You may like to develop your sustainability file as a file in your
source repository:

{ "sustainability": "https://raw.githubusercontent.com/{user}/{repo}/master/sustainability.json" }

The end point you specify should respond to unauthenticated GET
requests with a JSON body that conforms to the [sustainability data
schema](https://www.npmjs.com/package/sustainability-schema).

## files

The optional `files` field is an array of file patterns that describes
Expand Down
57 changes: 51 additions & 6 deletions lib/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ var iferr = require('iferr')
var validate = require('aproba')
var uniq = require('lodash.uniq')
var Bluebird = require('bluebird')
var getSustainability = require('get-sustainability')
var runParallel = require('run-parallel')

// npm internal utils
var npm = require('./npm.js')
Expand Down Expand Up @@ -138,6 +140,7 @@ var validateArgs = require('./install/validate-args.js')
var saveRequested = require('./install/save.js').saveRequested
var saveShrinkwrap = require('./install/save.js').saveShrinkwrap
var audit = require('./install/audit.js')
var sustainability = require('./install/sustainability.js')
var getSaveType = require('./install/save.js').getSaveType
var doSerialActions = require('./install/actions.js').doSerial
var doReverseSerialActions = require('./install/actions.js').doReverseSerial
Expand Down Expand Up @@ -780,9 +783,19 @@ Installer.prototype.printInstalled = function (cb) {
})
}
return Bluebird.try(() => {
if (!this.auditSubmission) return
return Bluebird.resolve(this.auditSubmission).timeout(10000).catch(() => null)
}).then((auditResult) => {
var tasks = {}
if (this.auditSubmission) {
tasks.audit = Bluebird.resolve(this.auditSubmission).timeout(10000).catch(() => null)
}
if (!npm.config.get('json') && !npm.config.get('parseable')) {
tasks.sustainability = Bluebird.fromCallback(function (cb) {
getSustainabilityData(diffs, cb)
})
}
return Bluebird.props(tasks)
}).then((results) => {
var auditResult = results.audit
var sustainabilityResult = results.sustainability
if (auditResult && !auditResult.metadata) {
log.warn('audit', 'Audit result from registry missing metadata. This is probably an issue with the registry.')
}
Expand All @@ -792,12 +805,42 @@ Installer.prototype.printInstalled = function (cb) {
} else if (npm.config.get('parseable')) {
return this.printInstalledForParseable(diffs, auditResult)
} else {
return this.printInstalledForHuman(diffs, auditResult)
return this.printInstalledForHuman(diffs, sustainabilityResult, auditResult)
}
}).asCallback(cb)
}

Installer.prototype.printInstalledForHuman = function (diffs, auditResult) {
function getSustainabilityData (diffs, cb) {
var records = []
diffs.forEach(function (action) {
var mutation = action[0]
var pkg = action[1]
if (pkg.failed) return
if (mutation === 'remove') return
var meta = pkg.package
var sustainability = meta.sustainability
if (sustainability && typeof sustainability === 'string') {
records.push({
name: meta.name,
version: meta.version,
uri: sustainability
})
}
})
runParallel(records.map(function (record) {
return function (done) {
getSustainability({ uri: record.uri }, function (err, data) {
if (err) return done() // eat errors
record.data = data
done(null, record)
})
}
}), function (_, results) {
cb(null, results.filter((e) => e))
})
}

Installer.prototype.printInstalledForHuman = function (diffs, sustainabilityResult, auditResult) {
var removed = 0
var added = 0
var updated = 0
Expand Down Expand Up @@ -872,7 +915,9 @@ Installer.prototype.printInstalledForHuman = function (diffs, auditResult) {
report += ' in ' + ((Date.now() - this.started) / 1000) + 's'

output(report)
return auditResult && audit.printInstallReport(auditResult)
if (auditResult) audit.printInstallReport(auditResult)
if (sustainabilityResult) sustainability.printInstallReport(sustainabilityResult)
else output('sustain')

function packages (num) {
return num + ' package' + (num > 1 ? 's' : '')
Expand Down
31 changes: 31 additions & 0 deletions lib/install/sustainability.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict'

const output = require('../utils/output.js')

exports.printInstallReport = function (result) {
if (result.length === 0) return
var report = 'Help sustain the packages you depend on!'
result.forEach(function (record) {
var name = record.name
var version = record.version
report += '\n' + name + '@' + version + ':\n'
report += record.data.contributors
.map(function (contributor) {
var line = ' ' + contributor.name
if (contributor.homepage) {
line += ' (' + contributor.homepage + ')'
}
line += '\n'
if (contributor.links) {
line += contributor.links
.map(function (link) {
return ' ' + link.uri
})
.join('\n') + '\n'
}
return line
})
.join('\n')
})
output(report)
}
53 changes: 48 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"fs-vacuum": "~1.2.10",
"fs-write-stream-atomic": "~1.0.10",
"gentle-fs": "^2.0.1",
"get-sustainability": "^2.0.0",
"glob": "^7.1.3",
"graceful-fs": "^4.1.15",
"has-unicode": "~2.0.1",
Expand Down Expand Up @@ -119,6 +120,7 @@
"request": "^2.88.0",
"retry": "^0.12.0",
"rimraf": "^2.6.3",
"run-parallel": "^1.1.9",
"safe-buffer": "^5.1.2",
"semver": "^5.6.0",
"sha": "~2.0.1",
Expand Down
68 changes: 68 additions & 0 deletions test/tap/install-sustainability.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use strict'
var path = require('path')
var test = require('tap').test
var Tacks = require('tacks')
var Dir = Tacks.Dir
var File = Tacks.File
var common = require('../common-tap.js')

var testdir = path.resolve(__dirname, path.basename(__filename, '.js'))
var fixture = new Tacks(Dir({
node_modules: Dir({
a: Dir({
'package.json': File({
name: 'a',
version: '1.0.0',
dependencies: {
b: '1.0.0'
}
}),
node_modules: Dir({
b: Dir({
'package.json': File({
name: 'b',
version: '1.0.0'
})
})
})
})
}),
'b-src': Dir({
'package.json': File({
name: 'b',
version: '1.0.0',
sustainability: 'https://raw.githubusercontent.com/kemitchell/get-sustainability.js/master/sustainability.json'
})
})
}))

function setup () {
cleanup()
fixture.create(testdir)
}

function cleanup () {
fixture.remove(testdir)
}

test('setup', function (t) {
setup()
t.end()
})

test('install', function (t) {
common.npm(['install', '--no-save', './b-src'], {cwd: testdir}, function (err, code, stdout, stderr) {
if (err) throw err
t.is(code, 0, 'installed successfully')
t.is(stderr, '', 'no warnings')
t.includes(stdout, 'sustain', 'says "sustain"')
t.includes(stdout, 'Kyle E. Mitchell', 'mentions contributor')
t.includes(stdout, 'kemitchell.com', 'mentions homepage')
t.end()
})
})

test('cleanup', function (t) {
cleanup()
t.end()
})