diff --git a/AUTHORS b/AUTHORS index e50da41e47212..69e158001e92f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -649,3 +649,6 @@ Iván Reinoso García Roy Marples Robert James Gabriel John Firebaugh +Kitten King +claudiahdz +Artem Sapegin diff --git a/CHANGELOG.md b/CHANGELOG.md index a77c898d984aa..e843fcaef659a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,52 @@ +## v6.10.3 (2019-08-06): + +### BUGFIXES + +* [`27cccfbda`](https://github.com/npm/cli/commit/27cccfbdac8526cc807b07f416355949b1372a9b) + [#223](https://github.com/npm/cli/pull/223) vulns → vulnerabilities in + npm audit output ([@sapegin](https://github.com/sapegin)) +* [`d5e865eb7`](https://github.com/npm/cli/commit/d5e865eb79329665a927cc2767b4395c03045dbb) + [#222](https://github.com/npm/cli/pull/222) + [#226](https://github.com/npm/cli/pull/226) install, doctor: don't crash + if registry unset ([@dmitrydvorkin](https://github.com/dmitrydvorkin), + [@isaacs](https://github.com/isaacs)) +* [`5b3890226`](https://github.com/npm/cli/commit/5b389022652abeb0e1c278a152550eb95bc6c452) + [#227](https://github.com/npm/cli/pull/227) + [npm.community#9167](https://npm.community/t/npm-err-cb-never-called-permission-denied/9167/5) + Handle unhandledRejections, tell user what to do when encountering an + `EACCES` error in the cache. ([@isaacs](https://github.com/isaacs)) + +### DEPENDENCIES + +* [`77516df6e`](https://github.com/npm/cli/commit/77516df6eac94a6d7acb5e9ca06feaa0868d779b) + `licensee@7.0.3` ([@isaacs](https://github.com/isaacs)) +* [`ceb993590`](https://github.com/npm/cli/commit/ceb993590e4e376a9a78264ce7bb4327fbbb37fe) + `query-string@6.8.2` ([@isaacs](https://github.com/isaacs)) +* [`4050b9189`](https://github.com/npm/cli/commit/4050b91898c60e9b22998cf82b70b9b822de592a) + `hosted-git-info@2.8.2` + * [#46](https://github.com/npm/hosted-git-info/issues/46) + [#43](https://github.com/npm/hosted-git-info/issues/43) + [#47](https://github.com/npm/hosted-git-info/pull/47) + [#44](https://github.com/npm/hosted-git-info/pull/44) Add support for + GitLab subgroups ([@mterrel](https://github.com/mterrel), + [@isaacs](https://github.com/isaacs), + [@ybiquitous](https://github.com/ybiquitous)) + * [`3b1d629`](https://github.com/npm/hosted-git-info/commit/3b1d629) + [#48](https://github.com/npm/hosted-git-info/issues/48) fix http + protocol using sshurl by default + ([@fengmk2](https://github.com/fengmk2)) + * [`5d4a8d7`](https://github.com/npm/hosted-git-info/commit/5d4a8d7) + ignore noCommittish on tarball url generation + ([@isaacs](https://github.com/isaacs)) + * [`1692435`](https://github.com/npm/hosted-git-info/commit/1692435) + use gist tarball url that works for anonymous gists + ([@isaacs](https://github.com/isaacs)) + * [`d5cf830`](https://github.com/npm/hosted-git-info/commit/d5cf8309be7af884032616c63ea302ce49dd321c) + Do not allow invalid gist urls ([@isaacs](https://github.com/isaacs)) + * [`e518222`](https://github.com/npm/hosted-git-info/commit/e5182224351183ce619dd5ef00019ae700ed37b7) + Use LRU cache to prevent unbounded memory consumption + ([@iarna](https://github.com/iarna)) + ## v6.10.2 (2019-07-23): tl;dr - Fixes several issues with the cache when npm is run as `sudo` on @@ -28,10 +77,6 @@ Unix systems. * [`235e5d6df`](https://github.com/npm/cli/commit/235e5d6df6f427585ec58425f1f3339d08f39d8a) ensure correct owner on cached all-packages metadata ([@isaacs](https://github.com/isaacs)) -* [`fa6acd2ea`](https://github.com/npm/cli/commit/fa6acd2ea035bf26c04d7885b534d03d5b77e7ba) - [npm.community#8450](https://npm.community/t/npm-audit-fails-with-child-requires-fails-because-requires-must-be-an-object/8540) - audit: report server error on failure - ([@isaacs](https://github.com/isaacs)) * [`e2d377bb6`](https://github.com/npm/cli/commit/e2d377bb6419d8a3c1d80a73dba46062b4dad336) [npm.community#8540](https://npm.community/t/npm-audit-fails-with-child-requires-fails-because-requires-must-be-an-object/8540) audit: report server error on failure diff --git a/bin/npm-cli.js b/bin/npm-cli.js index 705aa472e7e55..93eddc7a3c892 100755 --- a/bin/npm-cli.js +++ b/bin/npm-cli.js @@ -62,6 +62,7 @@ log.info('using', 'node@%s', process.version) process.on('uncaughtException', errorHandler) + process.on('unhandledRejection', errorHandler) if (conf.usage && npm.command !== 'help') { npm.argv.unshift(npm.command) diff --git a/lib/audit.js b/lib/audit.js index c86566403a2bf..7b694c13c5d7f 100644 --- a/lib/audit.js +++ b/lib/audit.js @@ -277,7 +277,7 @@ function auditCmd (args, cb) { output(` ${actions.review.size} vulnerabilit${actions.review.size === 1 ? 'y' : 'ies'} required manual review and could not be updated`) } if (actions.major.size) { - output(` ${actions.major.size} package update${actions.major.size === 1 ? '' : 's'} for ${actions.majorFixes.size} vuln${actions.majorFixes.size === 1 ? '' : 's'} involved breaking changes`) + output(` ${actions.major.size} package update${actions.major.size === 1 ? '' : 's'} for ${actions.majorFixes.size} vulnerabilit${actions.majorFixes.size === 1 ? 'y' : 'ies'} involved breaking changes`) if (installMajor) { output(' (installed due to `--force` option)') } else { diff --git a/lib/config/fetch-opts.js b/lib/config/fetch-opts.js deleted file mode 100644 index 213c293d6c7c9..0000000000000 --- a/lib/config/fetch-opts.js +++ /dev/null @@ -1,77 +0,0 @@ -'use strict' - -const url = require('url') - -module.exports.fromPacote = fromPacote - -function fromPacote (opts) { - return { - cache: getCacheMode(opts), - cacheManager: opts.cache, - ca: opts.ca, - cert: opts.cert, - headers: getHeaders('', opts.registry, opts), - key: opts.key, - localAddress: opts.localAddress, - maxSockets: opts.maxSockets, - proxy: opts.proxy, - referer: opts.refer, - retry: opts.retry, - strictSSL: !!opts.strictSSL, - timeout: opts.timeout, - uid: opts.uid, - gid: opts.gid - } -} - -function getCacheMode (opts) { - return opts.offline - ? 'only-if-cached' - : opts.preferOffline - ? 'force-cache' - : opts.preferOnline - ? 'no-cache' - : 'default' -} - -function getHeaders (uri, registry, opts) { - const headers = Object.assign({ - 'npm-in-ci': opts.isFromCI, - 'npm-scope': opts.projectScope, - 'npm-session': opts.npmSession, - 'user-agent': opts.userAgent, - 'referer': opts.refer - }, opts.headers) - // check for auth settings specific to this registry - let auth = ( - opts.auth && - opts.auth[registryKey(registry)] - ) || opts.auth - // If a tarball is hosted on a different place than the manifest, only send - // credentials on `alwaysAuth` - const shouldAuth = auth && ( - auth.alwaysAuth || - url.parse(uri).host === url.parse(registry).host - ) - if (shouldAuth && auth.token) { - headers.authorization = `Bearer ${auth.token}` - } else if (shouldAuth && auth.username && auth.password) { - const encoded = Buffer.from( - `${auth.username}:${auth.password}`, 'utf8' - ).toString('base64') - headers.authorization = `Basic ${encoded}` - } else if (shouldAuth && auth._auth) { - headers.authorization = `Basic ${auth._auth}` - } - return headers -} - -function registryKey (registry) { - const parsed = url.parse(registry) - const formatted = url.format({ - host: parsed.host, - pathname: parsed.pathname, - slashes: parsed.slashes - }) - return url.resolve(formatted, '.') -} diff --git a/lib/doctor.js b/lib/doctor.js index 95ede1bc87242..96094e6346d05 100644 --- a/lib/doctor.js +++ b/lib/doctor.js @@ -87,7 +87,7 @@ function makePretty (p) { const cacheStatus = p[8] ? `verified ${p[8].verifiedContent} tarballs` : 'notOk' const npmV = npm.version const nodeV = process.version.replace('v', '') - const registry = npm.config.get('registry') + const registry = npm.config.get('registry') || '' const list = [ ['npm ping', ping], ['npm -v', 'v' + npmV], diff --git a/lib/install/diff-trees.js b/lib/install/diff-trees.js index 346846fdc0ffe..147aa9b8e74f3 100644 --- a/lib/install/diff-trees.js +++ b/lib/install/diff-trees.js @@ -11,7 +11,7 @@ var moduleName = require('../utils/module-name.js') var isOnlyOptional = require('./is-only-optional.js') // we don't use get-requested because we're operating on files on disk, and -// we don't want to extropolate from what _should_ be there. +// we don't want to extrapolate from what _should_ be there. function pkgRequested (pkg) { return pkg._requested || (pkg._resolved && npa(pkg._resolved)) || (pkg._from && npa(pkg._from)) } diff --git a/lib/install/inflate-shrinkwrap.js b/lib/install/inflate-shrinkwrap.js index 395cc11191309..5da9418bbdb7a 100644 --- a/lib/install/inflate-shrinkwrap.js +++ b/lib/install/inflate-shrinkwrap.js @@ -74,7 +74,7 @@ function quotemeta (str) { } function tarballToVersion (name, tb) { - const registry = quotemeta(npm.config.get('registry')) + const registry = quotemeta(npm.config.get('registry') || '') .replace(/https?:/, 'https?:') .replace(/([^/])$/, '$1/') let matchRegTarball diff --git a/lib/shrinkwrap.js b/lib/shrinkwrap.js index bd8c0abbaa205..35e063d447956 100644 --- a/lib/shrinkwrap.js +++ b/lib/shrinkwrap.js @@ -121,7 +121,7 @@ function shrinkwrapDeps (deps, top, tree, seen) { if (isRegistry(requested)) { pkginfo.resolved = child.package._resolved } - // no integrity for git deps as integirty hashes are based on the + // no integrity for git deps as integrity hashes are based on the // tarball and we can't (yet) create consistent tarballs from a stable // source. if (requested.type !== 'git') { diff --git a/lib/utils/error-handler.js b/lib/utils/error-handler.js index 7cb43be2900ec..39e0035c27288 100644 --- a/lib/utils/error-handler.js +++ b/lib/utils/error-handler.js @@ -187,11 +187,12 @@ function errorHandler (er) { log.verbose('npm ', 'v' + npm.version) ;[ + 'code', + 'syscall', 'file', 'path', - 'code', - 'errno', - 'syscall' + 'dest', + 'errno' ].forEach(function (k) { var v = er[k] if (v) log.error(k, v) diff --git a/lib/utils/error-message.js b/lib/utils/error-message.js index bf5d65c0df3ca..ea8b05938c108 100644 --- a/lib/utils/error-message.js +++ b/lib/utils/error-message.js @@ -2,6 +2,7 @@ var npm = require('../npm.js') var util = require('util') var nameValidator = require('validate-npm-package-name') +var npmlog = require('npmlog') module.exports = errorMessage @@ -33,18 +34,42 @@ function errorMessage (er) { case 'EACCES': case 'EPERM': - short.push(['', er]) - detail.push([ - '', - [ - '\nThe operation was rejected by your operating system.', - (process.platform === 'win32' - ? 'It\'s possible that the file was already in use (by a text editor or antivirus),\nor that you lack permissions to access it.' - : 'It is likely you do not have the permissions to access this file as the current user'), - '\nIf you believe this might be a permissions issue, please double-check the', - 'permissions of the file and its containing directories, or try running', - 'the command again as root/Administrator (though this is not recommended).' - ].join('\n')]) + const isCachePath = typeof er.path === 'string' && + er.path.startsWith(npm.config.get('cache')) + const isCacheDest = typeof er.dest === 'string' && + er.dest.startsWith(npm.config.get('cache')) + + const isWindows = process.platform === 'win32' + + if (!isWindows && (isCachePath || isCacheDest)) { + // user probably doesn't need this, but still add it to the debug log + npmlog.verbose(er.stack) + short.push([ + '', + [ + '', + 'Your cache folder contains root-owned files, due to a bug in', + 'previous versions of npm which has since been addressed.', + '', + 'To permanently fix this problem, please run:', + ` sudo chown -R ${process.getuid()}:${process.getgid()} ${JSON.stringify(npm.config.get('cache'))}` + ].join('\n') + ]) + } else { + short.push(['', er]) + detail.push([ + '', + [ + '\nThe operation was rejected by your operating system.', + (process.platform === 'win32' + ? 'It\'s possible that the file was already in use (by a text editor or antivirus),\n' + + 'or that you lack permissions to access it.' + : 'It is likely you do not have the permissions to access this file as the current user'), + '\nIf you believe this might be a permissions issue, please double-check the', + 'permissions of the file and its containing directories, or try running', + 'the command again as root/Administrator.' + ].join('\n')]) + } break case 'ELIFECYCLE': diff --git a/node_modules/hosted-git-info/CHANGELOG.md b/node_modules/hosted-git-info/CHANGELOG.md index 7a8d5df968da3..9eedbb80b1ecc 100644 --- a/node_modules/hosted-git-info/CHANGELOG.md +++ b/node_modules/hosted-git-info/CHANGELOG.md @@ -2,6 +2,46 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +## [2.8.2](https://github.com/npm/hosted-git-info/compare/v2.8.1...v2.8.2) (2019-08-05) + + +### Bug Fixes + +* http protocol use sshurl by default ([3b1d629](https://github.com/npm/hosted-git-info/commit/3b1d629)), closes [#48](https://github.com/npm/hosted-git-info/issues/48) + + + + +## [2.8.1](https://github.com/npm/hosted-git-info/compare/v2.8.0...v2.8.1) (2019-08-05) + + +### Bug Fixes + +* ignore noCommittish on tarball url generation ([5d4a8d7](https://github.com/npm/hosted-git-info/commit/5d4a8d7)) +* use gist tarball url that works for anonymous gists ([1692435](https://github.com/npm/hosted-git-info/commit/1692435)) + + + + +# [2.8.0](https://github.com/npm/hosted-git-info/compare/v2.7.1...v2.8.0) (2019-08-05) + + +### Bug Fixes + +* Allow slashes in gitlab project section ([bbcf7b2](https://github.com/npm/hosted-git-info/commit/bbcf7b2)), closes [#46](https://github.com/npm/hosted-git-info/issues/46) [#43](https://github.com/npm/hosted-git-info/issues/43) +* **git-host:** disallow URI-encoded slash (%2F) in `path` ([3776fa5](https://github.com/npm/hosted-git-info/commit/3776fa5)), closes [#44](https://github.com/npm/hosted-git-info/issues/44) +* **gitlab:** Do not URL encode slashes in project name for GitLab https URL ([cbf04f9](https://github.com/npm/hosted-git-info/commit/cbf04f9)), closes [#47](https://github.com/npm/hosted-git-info/issues/47) +* do not allow invalid gist urls ([d5cf830](https://github.com/npm/hosted-git-info/commit/d5cf830)) +* **cache:** Switch to lru-cache to save ourselves from unlimited memory consumption ([e518222](https://github.com/npm/hosted-git-info/commit/e518222)), closes [#38](https://github.com/npm/hosted-git-info/issues/38) + + +### Features + +* give these objects a name ([60abaea](https://github.com/npm/hosted-git-info/commit/60abaea)) + + + ## [2.7.1](https://github.com/npm/hosted-git-info/compare/v2.7.0...v2.7.1) (2018-07-07) diff --git a/node_modules/hosted-git-info/git-host-info.js b/node_modules/hosted-git-info/git-host-info.js index 090a23251fbd2..d81be2050cd56 100644 --- a/node_modules/hosted-git-info/git-host-info.js +++ b/node_modules/hosted-git-info/git-host-info.js @@ -23,12 +23,14 @@ var gitHosts = module.exports = { 'domain': 'gitlab.com', 'treepath': 'tree', 'bugstemplate': 'https://{domain}/{user}/{project}/issues', - 'tarballtemplate': 'https://{domain}/{user}/{project}/repository/archive.tar.gz?ref={committish}' + 'httpstemplate': 'git+https://{auth@}{domain}/{user}/{projectPath}.git{#committish}', + 'tarballtemplate': 'https://{domain}/{user}/{project}/repository/archive.tar.gz?ref={committish}', + 'pathmatch': /^[/]([^/]+)[/](.+?)(?:[.]git|[/])?$/ }, gist: { 'protocols': [ 'git', 'git+ssh', 'git+https', 'ssh', 'https' ], 'domain': 'gist.github.com', - 'pathmatch': /^[/](?:([^/]+)[/])?([a-z0-9]+)(?:[.]git)?$/, + 'pathmatch': /^[/](?:([^/]+)[/])?([a-z0-9]{32,})(?:[.]git)?$/, 'filetemplate': 'https://gist.githubusercontent.com/{user}/{project}/raw{/committish}/{path}', 'bugstemplate': 'https://{domain}/{project}', 'gittemplate': 'git://{domain}/{project}.git{#committish}', @@ -40,7 +42,7 @@ var gitHosts = module.exports = { 'httpstemplate': 'git+https://{domain}/{project}.git{#committish}', 'shortcuttemplate': '{type}:{project}{#committish}', 'pathtemplate': '{project}{#committish}', - 'tarballtemplate': 'https://{domain}/{user}/{project}/archive/{committish}.tar.gz', + 'tarballtemplate': 'https://codeload.github.com/gist/{project}/tar.gz/{committish}', 'hashformat': function (fragment) { return 'file-' + formatHashFragment(fragment) } diff --git a/node_modules/hosted-git-info/git-host.js b/node_modules/hosted-git-info/git-host.js index 733648d84b6c9..ee5bb1af0ae9e 100644 --- a/node_modules/hosted-git-info/git-host.js +++ b/node_modules/hosted-git-info/git-host.js @@ -1,9 +1,24 @@ 'use strict' var gitHosts = require('./git-host-info.js') /* eslint-disable node/no-deprecated-api */ -var extend = Object.assign || require('util')._extend -var GitHost = module.exports = function (type, user, auth, project, committish, defaultRepresentation, opts) { +// copy-pasta util._extend from node's source, to avoid pulling +// the whole util module into peoples' webpack bundles. +/* istanbul ignore next */ +var extend = Object.assign || function _extend (target, source) { + // Don't do anything if source isn't an object + if (source === null || typeof source !== 'object') return target + + const keys = Object.keys(source) + let i = keys.length + while (i--) { + target[keys[i]] = source[keys[i]] + } + return target +} + +module.exports = GitHost +function GitHost (type, user, auth, project, committish, defaultRepresentation, opts) { var gitHostInfo = this gitHostInfo.type = type Object.keys(gitHosts[type]).forEach(function (key) { @@ -16,7 +31,6 @@ var GitHost = module.exports = function (type, user, auth, project, committish, gitHostInfo.default = defaultRepresentation gitHostInfo.opts = opts || {} } -GitHost.prototype = {} GitHost.prototype.hash = function () { return this.committish ? '#' + this.committish : '' @@ -32,24 +46,33 @@ GitHost.prototype._fill = function (template, opts) { if (self[key] != null && vars[key] == null) vars[key] = self[key] }) var rawAuth = vars.auth - var rawComittish = vars.committish + var rawcommittish = vars.committish var rawFragment = vars.fragment var rawPath = vars.path + var rawProject = vars.project Object.keys(vars).forEach(function (key) { - vars[key] = encodeURIComponent(vars[key]) + var value = vars[key] + if ((key === 'path' || key === 'project') && typeof value === 'string') { + vars[key] = value.split('/').map(function (pathComponent) { + return encodeURIComponent(pathComponent) + }).join('/') + } else { + vars[key] = encodeURIComponent(value) + } }) vars['auth@'] = rawAuth ? rawAuth + '@' : '' vars['#fragment'] = rawFragment ? '#' + this.hashformat(rawFragment) : '' vars.fragment = vars.fragment ? vars.fragment : '' vars['#path'] = rawPath ? '#' + this.hashformat(rawPath) : '' vars['/path'] = vars.path ? '/' + vars.path : '' + vars.projectPath = rawProject.split('/').map(encodeURIComponent).join('/') if (opts.noCommittish) { vars['#committish'] = '' vars['/tree/committish'] = '' - vars['/comittish'] = '' - vars.comittish = '' + vars['/committish'] = '' + vars.committish = '' } else { - vars['#committish'] = rawComittish ? '#' + rawComittish : '' + vars['#committish'] = rawcommittish ? '#' + rawcommittish : '' vars['/tree/committish'] = vars.committish ? '/' + vars.treepath + '/' + vars.committish : '' @@ -114,7 +137,8 @@ GitHost.prototype.path = function (opts) { return this._fill(this.pathtemplate, opts) } -GitHost.prototype.tarball = function (opts) { +GitHost.prototype.tarball = function (opts_) { + var opts = extend({}, opts_, { noCommittish: false }) return this._fill(this.tarballtemplate, opts) } @@ -127,5 +151,6 @@ GitHost.prototype.getDefaultRepresentation = function () { } GitHost.prototype.toString = function (opts) { - return (this[this.default] || this.sshurl).call(this, opts) + if (this.default && typeof this[this.default] === 'function') return this[this.default](opts) + return this.sshurl(opts) } diff --git a/node_modules/hosted-git-info/index.js b/node_modules/hosted-git-info/index.js index c1c2cc8996b9a..adae4604c4dbd 100644 --- a/node_modules/hosted-git-info/index.js +++ b/node_modules/hosted-git-info/index.js @@ -2,17 +2,18 @@ var url = require('url') var gitHosts = require('./git-host-info.js') var GitHost = module.exports = require('./git-host.js') +var LRU = require('lru-cache') +var cache = new LRU({max: 1000}) var protocolToRepresentationMap = { - 'git+ssh': 'sshurl', - 'git+https': 'https', - 'ssh': 'sshurl', - 'git': 'git' + 'git+ssh:': 'sshurl', + 'git+https:': 'https', + 'ssh:': 'sshurl', + 'git:': 'git' } function protocolToRepresentation (protocol) { - if (protocol.substr(-1) === ':') protocol = protocol.slice(0, -1) - return protocolToRepresentationMap[protocol] || protocol + return protocolToRepresentationMap[protocol] || protocol.slice(0, -1) } var authProtocols = { @@ -23,17 +24,15 @@ var authProtocols = { 'git+http:': true } -var cache = {} - module.exports.fromUrl = function (giturl, opts) { if (typeof giturl !== 'string') return var key = giturl + JSON.stringify(opts || {}) - if (!(key in cache)) { - cache[key] = fromUrl(giturl, opts) + if (!cache.has(key)) { + cache.set(key, fromUrl(giturl, opts)) } - return cache[key] + return cache.get(key) } function fromUrl (giturl, opts) { @@ -65,13 +64,17 @@ function fromUrl (giturl, opts) { var pathmatch = gitHostInfo.pathmatch var matched = parsed.path.match(pathmatch) if (!matched) return - if (matched[1] != null) user = decodeURIComponent(matched[1].replace(/^:/, '')) - if (matched[2] != null) project = decodeURIComponent(matched[2]) + if (matched[1] !== null && matched[1] !== undefined) { + user = decodeURIComponent(matched[1].replace(/^:/, '')) + } + project = decodeURIComponent(matched[2]) defaultRepresentation = protocolToRepresentation(parsed.protocol) } return new GitHost(gitHostName, user, auth, project, committish, defaultRepresentation, opts) } catch (ex) { - if (!(ex instanceof URIError)) throw ex + /* istanbul ignore else */ + if (ex instanceof URIError) { + } else throw ex } }).filter(function (gitHostInfo) { return gitHostInfo }) if (matches.length !== 1) return @@ -101,7 +104,6 @@ function fixupUnqualifiedGist (giturl) { } function parseGitUrl (giturl) { - if (typeof giturl !== 'string') giturl = '' + giturl var matched = giturl.match(/^([^@]+)@([^:/]+):[/]?((?:[^/]+[/])?[^/]+?)(?:[.]git)?(#.*)?$/) if (!matched) return url.parse(giturl) return { diff --git a/node_modules/hosted-git-info/package.json b/node_modules/hosted-git-info/package.json index de1ee3d7f6422..86e7ba59a7c9c 100644 --- a/node_modules/hosted-git-info/package.json +++ b/node_modules/hosted-git-info/package.json @@ -1,19 +1,19 @@ { - "_from": "hosted-git-info@latest", - "_id": "hosted-git-info@2.7.1", + "_from": "hosted-git-info@2.8.2", + "_id": "hosted-git-info@2.8.2", "_inBundle": false, - "_integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "_integrity": "sha512-CyjlXII6LMsPMyUzxpTt8fzh5QwzGqPmQXgY/Jyf4Zfp27t/FvfhwoE/8laaMUcMy816CkWF20I7NeQhwwY88w==", "_location": "/hosted-git-info", "_phantomChildren": {}, "_requested": { - "type": "tag", + "type": "version", "registry": true, - "raw": "hosted-git-info@latest", + "raw": "hosted-git-info@2.8.2", "name": "hosted-git-info", "escapedName": "hosted-git-info", - "rawSpec": "latest", + "rawSpec": "2.8.2", "saveSpec": null, - "fetchSpec": "latest" + "fetchSpec": "2.8.2" }, "_requiredBy": [ "#USER", @@ -21,10 +21,10 @@ "/normalize-package-data", "/npm-package-arg" ], - "_resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "_shasum": "97f236977bd6e125408930ff6de3eec6281ec047", - "_spec": "hosted-git-info@latest", - "_where": "/Users/zkat/Documents/code/work/npm", + "_resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.2.tgz", + "_shasum": "a35c3f355ac1249f1093c0c2a542ace8818c171a", + "_spec": "hosted-git-info@2.8.2", + "_where": "/Users/isaacs/dev/npm/cli", "author": { "name": "Rebecca Turner", "email": "me@re-becca.org", @@ -34,12 +34,15 @@ "url": "https://github.com/npm/hosted-git-info/issues" }, "bundleDependencies": false, + "dependencies": { + "lru-cache": "^5.1.1" + }, "deprecated": false, "description": "Provides metadata and conversions from repository urls for Github, Bitbucket and Gitlab", "devDependencies": { "standard": "^11.0.1", - "standard-version": "^4.3.0", - "tap": "^12.0.1" + "standard-version": "^4.4.0", + "tap": "^12.7.0" }, "files": [ "index.js", @@ -65,7 +68,7 @@ "prerelease": "npm t", "pretest": "standard", "release": "standard-version -s", - "test": "tap -J --nyc-arg=--all --coverage test" + "test": "tap -J --100 --no-esm test/*.js" }, - "version": "2.7.1" + "version": "2.8.2" } diff --git a/node_modules/query-string/index.js b/node_modules/query-string/index.js index da459c9404d0b..35c13e1a66565 100644 --- a/node_modules/query-string/index.js +++ b/node_modules/query-string/index.js @@ -202,7 +202,7 @@ function parse(input, options) { // http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters value = value === undefined ? null : decode(value, options); - if (options.parseNumbers && !Number.isNaN(Number(value))) { + if (options.parseNumbers && !Number.isNaN(Number(value)) && (typeof value === 'string' && value.trim() !== '')) { value = Number(value); } else if (options.parseBooleans && value !== null && (value.toLowerCase() === 'true' || value.toLowerCase() === 'false')) { value = value.toLowerCase() === 'true'; diff --git a/node_modules/query-string/package.json b/node_modules/query-string/package.json index 916b3fe5a1714..4df6b20ef0e91 100644 --- a/node_modules/query-string/package.json +++ b/node_modules/query-string/package.json @@ -1,27 +1,27 @@ { - "_from": "query-string@6.8.1", - "_id": "query-string@6.8.1", + "_from": "query-string@6.8.2", + "_id": "query-string@6.8.2", "_inBundle": false, - "_integrity": "sha512-g6y0Lbq10a5pPQpjlFuojfMfV1Pd2Jw9h75ypiYPPia3Gcq2rgkKiIwbkS6JxH7c5f5u/B/sB+d13PU+g1eu4Q==", + "_integrity": "sha512-J3Qi8XZJXh93t2FiKyd/7Ec6GNifsjKXUsVFkSBj/kjLsDylWhnCz4NT1bkPcKotttPW+QbKGqqPH8OoI2pdqw==", "_location": "/query-string", "_phantomChildren": {}, "_requested": { "type": "version", "registry": true, - "raw": "query-string@6.8.1", + "raw": "query-string@6.8.2", "name": "query-string", "escapedName": "query-string", - "rawSpec": "6.8.1", + "rawSpec": "6.8.2", "saveSpec": null, - "fetchSpec": "6.8.1" + "fetchSpec": "6.8.2" }, "_requiredBy": [ "#USER", "/" ], - "_resolved": "https://registry.npmjs.org/query-string/-/query-string-6.8.1.tgz", - "_shasum": "62c54a7ef37d01b538c8fd56f95740c81d438a26", - "_spec": "query-string@6.8.1", + "_resolved": "https://registry.npmjs.org/query-string/-/query-string-6.8.2.tgz", + "_shasum": "36cb7e452ae11a4b5e9efee83375e0954407b2f6", + "_spec": "query-string@6.8.2", "_where": "/Users/isaacs/dev/npm/cli", "author": { "name": "Sindre Sorhus", @@ -78,5 +78,5 @@ "scripts": { "test": "xo && ava && tsd" }, - "version": "6.8.1" + "version": "6.8.2" } diff --git a/package-lock.json b/package-lock.json index d325b301f1ba4..0cc22483e134e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "npm", - "version": "6.10.2", + "version": "6.10.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2464,9 +2464,12 @@ } }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.2.tgz", + "integrity": "sha512-CyjlXII6LMsPMyUzxpTt8fzh5QwzGqPmQXgY/Jyf4Zfp27t/FvfhwoE/8laaMUcMy816CkWF20I7NeQhwwY88w==", + "requires": { + "lru-cache": "^5.1.1" + } }, "http-cache-semantics": { "version": "3.8.1", @@ -3239,15 +3242,16 @@ } }, "licensee": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/licensee/-/licensee-7.0.2.tgz", - "integrity": "sha512-U30RSe5DI7HfKGM2LNgt/5QP7QEk4leKcRTp/6PorHHDeAoFsZn7SGwR/MKgP3xdk6cTD+G3SBdYwQGcZ1zPfA==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/licensee/-/licensee-7.0.3.tgz", + "integrity": "sha512-Rdwk+y+nOzkTKz68Gj0OOIy7ISkw+JJzSpn37hfHOXT6swkGfnKEt9+jRolWIFONiIhwv+W5E8K/pnXBfWLewQ==", "dev": true, "requires": { "@blueoak/list": "^1.0.2", "correct-license-metadata": "^1.0.1", "docopt": "^0.6.2", "fs-access": "^2.0.0", + "has": "^1.0.3", "json-parse-errback": "^2.0.1", "npm-license-corrections": "^1.0.0", "read-package-tree": "^5.2.1", @@ -3261,9 +3265,9 @@ }, "dependencies": { "semver": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.2.tgz", - "integrity": "sha512-z4PqiCpomGtWj8633oeAdXm1Kn1W++3T8epkZYnwiVgIYIJ0QHszhInYSJTYxebByQH7KVCEAn8R9duzZW2PhQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } @@ -4761,9 +4765,9 @@ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "query-string": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.8.1.tgz", - "integrity": "sha512-g6y0Lbq10a5pPQpjlFuojfMfV1Pd2Jw9h75ypiYPPia3Gcq2rgkKiIwbkS6JxH7c5f5u/B/sB+d13PU+g1eu4Q==", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.8.2.tgz", + "integrity": "sha512-J3Qi8XZJXh93t2FiKyd/7Ec6GNifsjKXUsVFkSBj/kjLsDylWhnCz4NT1bkPcKotttPW+QbKGqqPH8OoI2pdqw==", "requires": { "decode-uri-component": "^0.2.0", "split-on-first": "^1.0.0", diff --git a/package.json b/package.json index 00c0e1dc5c631..58631e96bb75d 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "6.10.2", + "version": "6.10.3", "name": "npm", "description": "a package manager for JavaScript", "keywords": [ @@ -63,7 +63,7 @@ "glob": "^7.1.4", "graceful-fs": "^4.2.0", "has-unicode": "~2.0.1", - "hosted-git-info": "^2.7.1", + "hosted-git-info": "^2.8.2", "iferr": "^1.0.2", "infer-owner": "^1.0.4", "inflight": "~1.0.6", @@ -114,7 +114,7 @@ "path-is-inside": "~1.0.2", "promise-inflight": "~1.0.1", "qrcode-terminal": "^0.12.0", - "query-string": "^6.8.1", + "query-string": "^6.8.2", "qw": "~1.0.1", "read": "~1.0.7", "read-cmd-shim": "~1.0.1", @@ -277,7 +277,7 @@ "devDependencies": { "deep-equal": "^1.0.1", "get-stream": "^4.1.0", - "licensee": "^7.0.2", + "licensee": "^7.0.3", "marked": "^0.6.3", "marked-man": "^0.6.0", "npm-registry-couchapp": "^2.7.3", diff --git a/scripts/pr b/scripts/pr new file mode 100755 index 0000000000000..30a3b05ffe3b1 --- /dev/null +++ b/scripts/pr @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +# Land a pull request +# Creates a PR-### branch, pulls the commits, opens up an interactive rebase to +# squash, and then annotates the commit with the changelog goobers +# +# Usage: +# pr [=origin] + +main () { + if [ "$1" = "finish" ]; then + shift + finish "$@" + return $? + fi + + local url="$(prurl "$@")" + local num=$(basename $url) + local prpath="${url#git@github.com:}" + local repo=${prpath%/pull/$num} + local prweb="https://github.com/$prpath" + local root="$(prroot "$url")" + local api="https://api.github.com/repos/${repo}/pulls/${num}" + local user=$(curl -s $api | json user.login) + local ref="$(prref "$url" "$root")" + local curhead="$(git show --no-patch --pretty=%H HEAD)" + local curbranch="$(git rev-parse --abbrev-ref HEAD)" + local cleanlines + IFS=$'\n' cleanlines=($(git status -s -uno)) + if [ ${#cleanlines[@]} -ne 0 ]; then + echo "working dir not clean" >&2 + IFS=$'\n' echo "${cleanlines[@]}" >&2 + echo "aborting PR merge" >&2 + fi + + # ok, ready to rock + branch=PR-$num + if [ "$curbranch" == "$branch" ]; then + echo "already on $branch, you're on your own" >&2 + return 1 + fi + + me=$(git config github.user || git config user.name) + if [ "$me" == "" ]; then + echo "run 'git config --add github.user '" >&2 + return 1 + fi + + exists=$(git show --no-patch --pretty=%H $branch 2>/dev/null) + if [ "$exists" == "" ]; then + git fetch origin pull/$num/head:$branch + git checkout $branch + else + git checkout $branch + git pull --rebase origin pull/$num/head + fi + + git rebase -i $curbranch # squash and test + + if [ $? -eq 0 ]; then + finish "${curbranch}" + else + echo "resolve conflicts and run: $0 finish "'"'${curbranch}'"' + fi +} + +# add the PR-URL to the last commit, after squashing +finish () { + if [ $# -eq 0 ]; then + echo "Usage: $0 finish (while on a PR-### branch)" >&2 + return 1 + fi + + local curbranch="$1" + local ref=$(cat .git/HEAD) + local prnum + case $ref in + "ref: refs/heads/PR-"*) + prnum=${ref#ref: refs/heads/PR-} + ;; + *) + echo "not on the PR-## branch any more!" >&2 + return 1 + ;; + esac + + local me=$(git config github.user || git config user.name) + if [ "$me" == "" ]; then + echo "run 'git config --add github.user '" >&2 + return 1 + fi + + set -x + + local url="$(prurl "$prnum")" + local num=$prnum + local prpath="${url#git@github.com:}" + local repo=${prpath%/pull/$num} + local prweb="https://github.com/$prpath" + local root="$(prroot "$url")" + + local api="https://api.github.com/repos/${repo}/pulls/${num}" + local user=$(curl -s $api | json user.login) + + local lastmsg="$(git log -1 --pretty=%B)" + local newmsg="${lastmsg} + +PR-URL: ${prweb} +Credit: @${user} +Close: #${num} +Reviewed-by: @${me} +" + git commit --amend -m "$newmsg" + git checkout $curbranch + git merge PR-${prnum} --ff-only + set +x +} + + +prurl () { + local url="$1" + if [ "$url" == "" ] && type pbpaste &>/dev/null; then + url="$(pbpaste)" + fi + if [[ "$url" =~ ^[0-9]+$ ]]; then + local us="$2" + if [ "$us" == "" ]; then + us="origin" + fi + local num="$url" + local o="$(git config --get remote.${us}.url)" + url="${o}" + url="${url#(git:\/\/|https:\/\/)}" + url="${url#git@}" + url="${url#github.com[:\/]}" + url="${url%.git}" + url="https://github.com/${url}/pull/$num" + fi + url=${url%/commits} + url=${url%/files} + url="$(echo $url | perl -p -e 's/#issuecomment-[0-9]+$//g')" + + local p='^https:\/\/github.com\/[^\/]+\/[^\/]+\/pull\/[0-9]+$' + if ! [[ "$url" =~ $p ]]; then + echo "Usage:" + echo " $0 " + echo " $0 [=origin]" + type pbpaste &>/dev/null && + echo "(will read url/id from clipboard if not specified)" + exit 1 + fi + url="${url/https:\/\/github\.com\//git@github.com:}" + echo "$url" +} + +prroot () { + local url="$1" + echo "${url/\/pull\/+([0-9])/}" +} + +prref () { + local url="$1" + local root="$2" + echo "refs${url:${#root}}/head" +} + +main "$@" diff --git a/test/tap/cache-eacces-error-message.js b/test/tap/cache-eacces-error-message.js new file mode 100644 index 0000000000000..aa112eba43921 --- /dev/null +++ b/test/tap/cache-eacces-error-message.js @@ -0,0 +1,38 @@ +const npm = require('../../lib/npm.js') +const t = require('tap') + +if (process.platform === 'win32') { + t.plan(0, 'this is a unix-only thing') + process.exit(0) +} + +const errorMessage = require('../../lib/utils/error-message.js') + +const common = require('../common-tap.js') + +t.plan(1) + +npm.load({ cache: common.cache }, () => { + npm.config.set('cache', common.cache) + const er = new Error('access is e, i am afraid') + er.code = 'EACCES' + er.errno = -13 + er.path = common.cache + '/src' + er.dest = common.cache + '/to' + + t.match(errorMessage(er), { + summary: [ + [ + '', + new RegExp('\n' + + 'Your cache folder contains root-owned files, due to a bug in\n' + + 'previous versions of npm which has since been addressed.\n' + + '\n' + + 'To permanently fix this problem, please run:\n' + + ' sudo chown -R [0-9]+:[0-9]+ ".*npm_cache_cache-eacces-error-message"' + ) + ] + ], + detail: [] + }, 'get the helpful error message') +}) diff --git a/test/tap/cache-shasum-fork.js b/test/tap/cache-shasum-fork.js index e035c78111af8..fade5ffb646c5 100644 --- a/test/tap/cache-shasum-fork.js +++ b/test/tap/cache-shasum-fork.js @@ -3,8 +3,6 @@ var path = require('path') var mkdirp = require('mkdirp') var mr = require('npm-registry-mock') -var osenv = require('osenv') -var rimraf = require('rimraf') var test = require('tap').test var common = require('../common-tap.js') @@ -19,7 +17,8 @@ var cache = common.cache var server test('setup', function (t) { - setup() + mkdirp.sync(path.join(pkg, 'node_modules')) + process.chdir(pkg) t.comment('test for https://github.com/npm/npm/issues/3265') mr({ port: common.port }, function (er, s) { server = s @@ -28,7 +27,6 @@ test('setup', function (t) { }) test('npm cache - install from fork', function (t) { - setup() common.npm( [ '--loglevel', 'silent', @@ -60,7 +58,6 @@ test('npm cache - install from fork', function (t) { // Now install the real 1.5.1. test('npm cache - install from origin', function (t) { - setup() common.npm( [ '--loglevel', 'silent', @@ -91,17 +88,5 @@ test('npm cache - install from origin', function (t) { test('cleanup', function (t) { server.close() - cleanup() t.end() }) - -function cleanup () { - process.chdir(osenv.tmpdir()) - rimraf.sync(pkg) -} - -function setup () { - mkdirp.sync(cache) - mkdirp.sync(path.join(pkg, 'node_modules')) - process.chdir(pkg) -} diff --git a/test/tap/doctor.js b/test/tap/doctor.js index 7b07e0f39b7d8..9285518c2e10e 100644 --- a/test/tap/doctor.js +++ b/test/tap/doctor.js @@ -5,9 +5,8 @@ const http = require('http') const mr = require('npm-registry-mock') const npm = require('../../lib/npm.js') const path = require('path') -const rimraf = require('rimraf') const Tacks = require('tacks') -const test = require('tap').test +const t = require('tap') const which = require('which') const Dir = Tacks.Dir @@ -44,12 +43,23 @@ const npmResponse = { } } -test('setup', (t) => { +let nodeServer + +t.teardown(() => { + if (server) { + server.close() + } + if (nodeServer) { + nodeServer.close() + } +}) + +t.test('setup', (t) => { const port = common.port + 1 - http.createServer(function (q, s) { + nodeServer = http.createServer(function (q, s) { s.end(JSON.stringify([{lts: true, version: '0.0.0'}])) - this.close() - }).listen(port, () => { + }) + nodeServer.listen(port, () => { node_url = 'http://localhost:' + port mr({port: common.port}, (err, s) => { t.ifError(err, 'registry mocked successfully') @@ -78,7 +88,7 @@ test('setup', (t) => { }) }) -test('npm doctor', function (t) { +t.test('npm doctor', function (t) { npm.commands.doctor({'node-url': node_url}, true, function (e, list) { t.ifError(e, 'npm loaded successfully') t.same(list.length, 9, 'list should have 9 prop') @@ -93,13 +103,29 @@ test('npm doctor', function (t) { which('git', function (e, resolvedPath) { t.ifError(e, 'git command is installed') t.same(list[4][1], resolvedPath, 'which git') - server.close() t.done() }) }) }) -test('cleanup', (t) => { - rimraf.sync(ROOT) - t.done() +t.test('npm doctor works without registry', function (t) { + npm.config.set('registry', false) + npm.commands.doctor({'node-url': node_url}, true, function (e, list) { + t.ifError(e, 'npm loaded successfully') + t.same(list.length, 9, 'list should have 9 prop') + t.same(list[0][1], 'OK', 'npm ping') + t.same(list[1][1], 'v' + npm.version, 'npm -v') + t.same(list[2][1], process.version, 'node -v') + t.same(list[3][1], '', 'no registry, but no crash') + t.same(list[5][1], 'ok', 'Perms check on cached files') + t.same(list[6][1], 'ok', 'Perms check on global node_modules') + t.same(list[7][1], 'ok', 'Perms check on local node_modules') + t.match(list[8][1], /^verified \d+ tarballs?$/, 'Cache verified') + which('git', function (e, resolvedPath) { + t.ifError(e, 'git command is installed') + t.same(list[4][1], resolvedPath, 'which git') + server.close() + t.done() + }) + }) }) diff --git a/test/tap/false-name.js b/test/tap/false-name.js index 541bacc7eda53..57d2a2ad2f8dd 100644 --- a/test/tap/false-name.js +++ b/test/tap/false-name.js @@ -58,7 +58,7 @@ test('not every pkg.name can be required', function (t) { ) t.ok( existsSync(path.join(pkg, 'node_modules', 'test-package')), - 'test-pacakge subdep installed OK' + 'test-package subdep installed OK' ) t.end() } diff --git a/test/tap/install-without-registry-config.js b/test/tap/install-without-registry-config.js new file mode 100644 index 0000000000000..852bd777ced5a --- /dev/null +++ b/test/tap/install-without-registry-config.js @@ -0,0 +1,35 @@ +const t = require('tap') +const { pkg, npm } = require('../common-tap.js') +const { writeFileSync, statSync, readFileSync } = require('fs') +const mkdirp = require('mkdirp') +const proj = pkg + '/project' +const dep = pkg + '/dep' +mkdirp.sync(proj) +mkdirp.sync(dep) +writeFileSync(dep + '/package.json', JSON.stringify({ + name: 'dependency', + version: '1.2.3' +})) +writeFileSync(proj + '/package.json', JSON.stringify({ + name: 'project', + version: '4.2.0' +})) + +const runTest = t => npm([ + 'install', + '../dep', + '--no-registry' +], { cwd: proj }).then(([code, out, err]) => { + t.equal(code, 0) + t.match(out, /^\+ dependency@1\.2\.3\n.* 1 package in [0-9.]+m?s\n$/) + t.equal(err, '') + const data = readFileSync(proj + '/node_modules/dependency/package.json', 'utf8') + t.same(JSON.parse(data), { + name: 'dependency', + version: '1.2.3' + }, 'dep got installed') + t.ok(statSync(proj + '/package-lock.json').isFile(), 'package-lock exists') +}) + +t.test('install without a registry, no package lock', t => runTest(t)) +t.test('install without a registry, with package lock', t => runTest(t))