diff options
-rw-r--r-- | docs/content/cli-commands/npm-bugs.md | 9 | ||||
-rw-r--r-- | docs/content/cli-commands/npm-docs.md | 12 | ||||
-rw-r--r-- | docs/content/cli-commands/npm-repo.md | 9 | ||||
-rw-r--r-- | docs/content/using-npm/config.md | 9 | ||||
-rw-r--r-- | lib/bugs.js | 72 | ||||
-rw-r--r-- | lib/config/defaults.js | 2 | ||||
-rw-r--r-- | lib/docs.js | 73 | ||||
-rw-r--r-- | lib/doctor/get-latest-npm-version.js | 23 | ||||
-rw-r--r-- | lib/fetch-package-metadata.js | 119 | ||||
-rw-r--r-- | lib/fetch-package-metadata.md | 37 | ||||
-rw-r--r-- | lib/repo.js | 96 | ||||
-rw-r--r-- | lib/utils/hosted-git-info-from-manifest.js | 14 | ||||
-rw-r--r-- | lib/utils/open-url.js | 9 | ||||
-rw-r--r-- | test/lib/bugs.js | 96 | ||||
-rw-r--r-- | test/lib/docs.js | 85 | ||||
-rw-r--r-- | test/lib/repo.js | 190 | ||||
-rw-r--r-- | test/lib/utils/hosted-git-info-from-manifest.js | 21 | ||||
-rw-r--r-- | test/tap/add-remote-git-file.js | 90 | ||||
-rw-r--r-- | test/tap/bugs.js | 172 | ||||
-rw-r--r-- | test/tap/fetch-package-metadata.js | 48 |
20 files changed, 638 insertions, 548 deletions
diff --git a/docs/content/cli-commands/npm-bugs.md b/docs/content/cli-commands/npm-bugs.md index 6e59f2bd1..1e7272cec 100644 --- a/docs/content/cli-commands/npm-bugs.md +++ b/docs/content/cli-commands/npm-bugs.md @@ -10,7 +10,7 @@ description: Bugs for a package in a web browser maybe ### Synopsis ```bash -npm bugs [<pkgname>] +npm bugs [<pkgname> [<pkgname> ...]] aliases: issues ``` @@ -27,10 +27,15 @@ a `package.json` in the current folder and use the `name` property. #### browser * Default: OS X: `"open"`, Windows: `"start"`, Others: `"xdg-open"` -* Type: String +* Type: String or Boolean The browser that is called by the `npm bugs` command to open websites. +Set to `false` to suppress browser behavior and instead print urls to +terminal. + +Set to `true` to use default system URL opener. + #### registry * Default: https://registry.npmjs.org/ diff --git a/docs/content/cli-commands/npm-docs.md b/docs/content/cli-commands/npm-docs.md index f157e200d..ffa5db5ac 100644 --- a/docs/content/cli-commands/npm-docs.md +++ b/docs/content/cli-commands/npm-docs.md @@ -12,9 +12,8 @@ description: Docs for a package in a web browser maybe ```bash npm docs [<pkgname> [<pkgname> ...]] -npm docs . -npm home [<pkgname> [<pkgname> ...]] -npm home . + +aliases: home ``` ### Description @@ -30,10 +29,15 @@ the current folder and use the `name` property. #### browser * Default: OS X: `"open"`, Windows: `"start"`, Others: `"xdg-open"` -* Type: String +* Type: String or Boolean The browser that is called by the `npm docs` command to open websites. +Set to `false` to suppress browser behavior and instead print urls to +terminal. + +Set to `true` to use default system URL opener. + #### registry * Default: https://registry.npmjs.org/ diff --git a/docs/content/cli-commands/npm-repo.md b/docs/content/cli-commands/npm-repo.md index 48021d9af..04544347c 100644 --- a/docs/content/cli-commands/npm-repo.md +++ b/docs/content/cli-commands/npm-repo.md @@ -11,7 +11,7 @@ description: Open package repository page in the browser ### Synopsis ```bash -npm repo [<pkg>] +npm repo [<pkgname> [<pkgname> ...]] ``` ### Description @@ -26,10 +26,15 @@ a `package.json` in the current folder and use the `name` property. #### browser * Default: OS X: `"open"`, Windows: `"start"`, Others: `"xdg-open"` -* Type: String +* Type: String or Boolean The browser that is called by the `npm repo` command to open websites. +Set to `false` to suppress browser behavior and instead print urls to +terminal. + +Set to `true` to use default system URL opener. + ### See Also * [npm docs](/cli-commands/docs) diff --git a/docs/content/using-npm/config.md b/docs/content/using-npm/config.md index 8aa149198..e444d5fb8 100644 --- a/docs/content/using-npm/config.md +++ b/docs/content/using-npm/config.md @@ -227,9 +227,14 @@ ostensibly Unix systems. #### browser * Default: OS X: `"open"`, Windows: `"start"`, Others: `"xdg-open"` -* Type: String +* Type: String or Boolean + +The browser that is called by npm commands to open websites. + +Set to `false` to suppress browser behavior and instead print urls to +terminal. -The browser that is called by the `npm docs` command to open websites. +Set to `true` to use default system URL opener. #### ca diff --git a/lib/bugs.js b/lib/bugs.js index 10300d1e1..55f81989a 100644 --- a/lib/bugs.js +++ b/lib/bugs.js @@ -1,31 +1,49 @@ -module.exports = bugs - -var log = require('npmlog') -var openUrl = require('./utils/open-url') -var fetchPackageMetadata = require('./fetch-package-metadata.js') -var usage = require('./utils/usage') - -bugs.usage = usage( - 'bugs', - 'npm bugs [<pkgname>]' -) - -bugs.completion = function (opts, cb) { - // FIXME: there used to be registry completion here, but it stopped making - // sense somewhere around 50,000 packages on the registry - cb() -} +const log = require('npmlog') +const pacote = require('pacote') +const { promisify } = require('util') +const openUrl = promisify(require('./utils/open-url.js')) +const usageUtil = require('./utils/usage.js') +const npm = require('./npm.js') +const hostedFromMani = require('./utils/hosted-git-info-from-manifest.js') + +const usage = usageUtil('bugs', 'npm bugs [<pkgname>]') +const completion = (opts, cb) => cb(null, []) -function bugs (args, cb) { - var n = args.length ? args[0] : '.' - fetchPackageMetadata(n, '.', {fullMetadata: true}, function (er, d) { - if (er) return cb(er) +const cmd = (args, cb) => bugs(args).then(() => cb()).catch(cb) - var url = d.bugs && ((typeof d.bugs === 'string') ? d.bugs : d.bugs.url) - if (!url) { - url = 'https://www.npmjs.org/package/' + d.name +const bugs = async args => { + if (!args || !args.length) { + args = ['.'] + } + await Promise.all(args.map(pkg => getBugs(pkg))) +} + +const getBugsUrl = mani => { + if (mani.bugs) { + if (typeof mani.bugs === 'string') { + return mani.bugs + } + if (typeof mani.bugs === 'object' && mani.bugs.url) { + return mani.bugs.url } - log.silly('bugs', 'url', url) - openUrl(url, 'bug list available at the following URL', cb) - }) + } + + // try to get it from the repo, if possible + const info = hostedFromMani(mani) + if (info) { + return info.bugs() + } + + // just send them to the website, hopefully that has some info! + return `https://www.npmjs.com/package/${mani.name}` } + +const getBugs = async pkg => { + const opts = { ...npm.flatOptions, fullMetadata: true } + const mani = await pacote.manifest(pkg, { fullMetadata: true }) + const url = getBugsUrl(mani) + log.silly('bugs', 'url', url) + await openUrl(url, `${mani.name} bug list available at the following URL`) +} + +module.exports = Object.assign(cmd, { usage, completion }) diff --git a/lib/config/defaults.js b/lib/config/defaults.js index 0ca1472eb..b47adf673 100644 --- a/lib/config/defaults.js +++ b/lib/config/defaults.js @@ -269,7 +269,7 @@ exports.types = { 'auth-type': ['legacy', 'sso', 'saml', 'oauth'], 'before': [null, Date], 'bin-links': Boolean, - browser: [null, String], + browser: [null, Boolean, String], ca: [null, String, Array], cafile: path, cache: path, diff --git a/lib/docs.js b/lib/docs.js index 6d67da4e1..4a1dbf7ed 100644 --- a/lib/docs.js +++ b/lib/docs.js @@ -1,41 +1,42 @@ -module.exports = docs - -var openUrl = require('./utils/open-url') -var log = require('npmlog') -var fetchPackageMetadata = require('./fetch-package-metadata.js') -var usage = require('./utils/usage') - -docs.usage = usage( - 'docs', - 'npm docs <pkgname>' + - '\nnpm docs .' -) -docs.completion = function (opts, cb) { - // FIXME: there used to be registry completion here, but it stopped making - // sense somewhere around 50,000 packages on the registry - cb() +const log = require('npmlog') +const pacote = require('pacote') +const { promisify } = require('util') +const openUrl = promisify(require('./utils/open-url.js')) +const usageUtil = require('./utils/usage.js') +const npm = require('./npm.js') +const hostedFromMani = require('./utils/hosted-git-info-from-manifest.js') + +const usage = usageUtil('docs', 'npm docs [<pkgname> [<pkgname> ...]]') +const completion = (opts, cb) => cb(null, []) + +const cmd = (args, cb) => docs(args).then(() => cb()).catch(cb) + +const docs = async args => { + if (!args || !args.length) { + args = ['.'] + } + await Promise.all(args.map(pkg => getDocs(pkg))) } -function docs (args, cb) { - if (!args || !args.length) args = ['.'] - var pending = args.length - log.silly('docs', args) - args.forEach(function (proj) { - getDoc(proj, function (err) { - if (err) { - return cb(err) - } - --pending || cb() - }) - }) +const getDocsUrl = mani => { + if (mani.homepage) { + return mani.homepage + } + + const info = hostedFromMani(mani) + if (info) { + return info.docs() + } + + return 'https://www.npmjs.com/package/' + mani.name } -function getDoc (project, cb) { - log.silly('getDoc', project) - fetchPackageMetadata(project, '.', {fullMetadata: true}, function (er, d) { - if (er) return cb(er) - var url = d.homepage - if (!url) url = 'https://www.npmjs.org/package/' + d.name - return openUrl(url, 'docs available at the following URL', cb) - }) +const getDocs = async pkg => { + const opts = { ...npm.flatOptions, fullMetadata: true } + const mani = await pacote.manifest(pkg, opts) + const url = getDocsUrl(mani) + log.silly('docs', 'url', url) + await openUrl(url, `${mani.name} docs available at the following URL`) } + +module.exports = Object.assign(cmd, { usage, completion }) diff --git a/lib/doctor/get-latest-npm-version.js b/lib/doctor/get-latest-npm-version.js index 5a096ab89..83b600396 100644 --- a/lib/doctor/get-latest-npm-version.js +++ b/lib/doctor/get-latest-npm-version.js @@ -1,14 +1,19 @@ -var log = require('npmlog') -var fetchPackageMetadata = require('../fetch-package-metadata') +const log = require('npmlog') +const pacote = require('pacote') +const npm = require('../npm.js') -function getLatestNpmVersion (cb) { - var tracker = log.newItem('getLatestNpmVersion', 1) +const getLatestNpmVersion = async cb => { + const tracker = log.newItem('getLatestNpmVersion', 1) tracker.info('getLatestNpmVersion', 'Getting npm package information') - fetchPackageMetadata('npm@latest', '.', {}, function (err, d) { - tracker.finish() - if (err) { return cb(err) } - cb(null, d.version) - }) + let version = null + let error = null + try { + version = (await pacote.manifest('npm@latest')).version + } catch (er) { + error = er + } + tracker.finish() + return cb(error, version) } module.exports = getLatestNpmVersion diff --git a/lib/fetch-package-metadata.js b/lib/fetch-package-metadata.js deleted file mode 100644 index c4f46f513..000000000 --- a/lib/fetch-package-metadata.js +++ /dev/null @@ -1,119 +0,0 @@ -'use strict' - -const deprCheck = require('./utils/depr-check') -const path = require('path') -const log = require('npmlog') -const readPackageTree = require('read-package-tree') -const rimraf = require('rimraf') -const validate = require('aproba') -const npa = require('npm-package-arg') -const npm = require('./npm') -let npmConfig -const npmlog = require('npmlog') -const limit = require('call-limit') -const tempFilename = require('./utils/temp-filename') -const pacote = require('pacote') -const isWindows = require('./utils/is-windows.js') - -function andLogAndFinish (spec, tracker, done) { - validate('SOF|SZF|OOF|OZF', [spec, tracker, done]) - return (er, pkg) => { - if (er) { - log.silly('fetchPackageMetaData', 'error for ' + String(spec), er.message) - if (tracker) tracker.finish() - } - return done(er, pkg) - } -} - -const LRUCache = require('lru-cache') -const CACHE = new LRUCache({ - max: 300 * 1024 * 1024, - length: (p) => p._contentLength -}) - -module.exports = limit(fetchPackageMetadata, npm.limit.fetch) -function fetchPackageMetadata (spec, where, opts, done) { - validate('SSOF|SSFZ|OSOF|OSFZ', [spec, where, opts, done]) - - if (!done) { - done = opts - opts = {} - } - var tracker = opts.tracker - const logAndFinish = andLogAndFinish(spec, tracker, done) - - if (typeof spec === 'object') { - var dep = spec - } else { - dep = npa(spec) - } - if (!isWindows && dep.type === 'directory' && /^[a-zA-Z]:/.test(dep.fetchSpec)) { - var err = new Error(`Can't install from windows path on a non-windows system: ${dep.fetchSpec.replace(/[/]/g, '\\')}`) - err.code = 'EWINDOWSPATH' - return logAndFinish(err) - } - if (!npmConfig) { - npmConfig = require('./config/figgy-config.js') - } - pacote.manifest(dep, npmConfig({ - annotate: true, - fullMetadata: opts.fullMetadata, - log: tracker || npmlog, - memoize: CACHE, - where: where - })).then( - (pkg) => logAndFinish(null, deprCheck(pkg)), - (err) => { - if (dep.type !== 'directory') return logAndFinish(err) - if (err.code === 'ENOTDIR') { - var enolocal = new Error(`Could not install "${path.relative(process.cwd(), dep.fetchSpec)}" as it is not a directory and is not a file with a name ending in .tgz, .tar.gz or .tar`) - enolocal.code = 'ENOLOCAL' - if (err.stack) enolocal.stack = err.stack - return logAndFinish(enolocal) - } else if (err.code === 'ENOPACKAGEJSON') { - var enopackage = new Error(`Could not install from "${path.relative(process.cwd(), dep.fetchSpec)}" as it does not contain a package.json file.`) - enopackage.code = 'ENOLOCAL' - if (err.stack) enopackage.stack = err.stack - return logAndFinish(enopackage) - } else { - return logAndFinish(err) - } - } - ) -} - -module.exports.addBundled = addBundled -function addBundled (pkg, next) { - validate('OF', arguments) - if (pkg._bundled !== undefined) return next(null, pkg) - - if (!pkg.bundleDependencies && pkg._requested.type !== 'directory') return next(null, pkg) - const requested = pkg._requested || npa(pkg._from) - if (requested.type === 'directory') { - pkg._bundled = null - return readPackageTree(pkg._requested.fetchSpec, function (er, tree) { - if (tree) pkg._bundled = tree.children - return next(null, pkg) - }) - } - pkg._bundled = null - const target = tempFilename('unpack') - if (!npmConfig) { - npmConfig = require('./config/figgy-config.js') - } - const opts = npmConfig({integrity: pkg._integrity}) - pacote.extract(pkg._resolved || pkg._requested || npa.resolve(pkg.name, pkg.version), target, opts).then(() => { - log.silly('addBundled', 'read tarball') - readPackageTree(target, (err, tree) => { - if (err) { return next(err) } - log.silly('cleanup', 'remove extracted module') - rimraf(target, function () { - if (tree) { - pkg._bundled = tree.children - } - next(null, pkg) - }) - }) - }, next) -} diff --git a/lib/fetch-package-metadata.md b/lib/fetch-package-metadata.md deleted file mode 100644 index 7b4562341..000000000 --- a/lib/fetch-package-metadata.md +++ /dev/null @@ -1,37 +0,0 @@ -fetch-package-metadata ----------------------- - - const fetchPackageMetadata = require("npm/lib/fetch-package-metadata") - fetchPackageMetadata(spec, contextdir, callback) - -This will get package metadata (and if possible, ONLY package metadata) for -a specifier as passed to `npm install` et al, eg `npm@next` or `npm@^2.0.3` - -## fetchPackageMetadata(*spec*, *contextdir*, *tracker*, *callback*) - -* *spec* **string** | **object** -- The package specifier, can be anything npm can - understand (see [realize-package-specifier]), or it can be the result from - realize-package-specifier or npm-package-arg (for non-local deps). - -* *contextdir* **string** -- The directory from which relative paths to - local packages should be resolved. - -* *tracker* **object** -- **(optional)** An are-we-there-yet tracker group as - provided by `npm.log.newGroup()`. - -* *callback* **function (er, package)** -- Called when the package information - has been loaded. `package` is the object for of the `package.json` - matching the requested spec. In the case of named packages, it comes from - the registry and thus may not exactly match what's found in the associated - tarball. - -[realize-package-specifier]: (https://github.com/npm/realize-package-specifier) - -In the case of tarballs and git repos, it will use the cache to download -them in order to get the package metadata. For named packages, only the -metadata is downloaded (eg https://registry.npmjs.org/package). For local -directories, the package.json is read directly. For local tarballs, the -tarball is streamed in memory and just the package.json is extracted from -it. (Due to the nature of tars, having the package.json early in the file -will result in it being loaded faster– the extractor short-circuits the -uncompress/untar streams as best as it can.) diff --git a/lib/repo.js b/lib/repo.js index b930402ae..455ca9f33 100644 --- a/lib/repo.js +++ b/lib/repo.js @@ -1,50 +1,72 @@ -module.exports = repo +const log = require('npmlog') +const pacote = require('pacote') +const { promisify } = require('util') +const openUrl = promisify(require('./utils/open-url.js')) +const usageUtil = require('./utils/usage.js') +const npm = require('./npm.js') +const hostedFromMani = require('./utils/hosted-git-info-from-manifest.js') +const { URL } = require('url') -repo.usage = 'npm repo [<pkg>]' +const usage = usageUtil('repo', 'npm repo [<pkgname> [<pkgname> ...]]') +const completion = (opts, cb) => cb(null, []) -const openUrl = require('./utils/open-url') -const hostedGitInfo = require('hosted-git-info') -const url_ = require('url') -const fetchPackageMetadata = require('./fetch-package-metadata.js') +const cmd = (args, cb) => repo(args).then(() => cb()).catch(cb) -repo.completion = function (opts, cb) { - // FIXME: there used to be registry completion here, but it stopped making - // sense somewhere around 50,000 packages on the registry - cb() +const repo = async args => { + if (!args || !args.length) { + args = ['.'] + } + await Promise.all(args.map(pkg => getRepo(pkg))) } -function repo (args, cb) { - const n = args.length ? args[0] : '.' - fetchPackageMetadata(n, '.', {fullMetadata: true}, function (er, d) { - if (er) return cb(er) - getUrlAndOpen(d, cb) - }) -} +const getRepo = async pkg => { + const opts = { ...npm.flatOptions, fullMetadata: true } + const mani = await pacote.manifest(pkg, opts) + + const r = mani.repository + const rurl = !r ? null + : typeof r === 'string' ? r + : typeof r === 'object' && typeof r.url === 'string' ? r.url + : null + + if (!rurl) { + throw Object.assign(new Error('no repository'), { + pkgid: pkg + }) + } -function getUrlAndOpen (d, cb) { - const r = d.repository - if (!r) return cb(new Error('no repository')) - // XXX remove this when npm@v1.3.10 from node 0.10 is deprecated - // from https://github.com/npm/npm-www/issues/418 - const info = hostedGitInfo.fromUrl(r.url) - const url = info ? info.browse() : unknownHostedUrl(r.url) + const info = hostedFromMani(mani) + const url = info ? info.browse() : unknownHostedUrl(rurl) - if (!url) return cb(new Error('no repository: could not get url')) + if (!url) { + throw Object.assign(new Error('no repository: could not get url'), { + pkgid: pkg + }) + } - openUrl(url, 'repository available at the following URL', cb) + log.silly('docs', 'url', url) + await openUrl(url, `${mani.name} repo available at the following URL`) } -function unknownHostedUrl (url) { +const unknownHostedUrl = url => { try { - const idx = url.indexOf('@') - if (idx !== -1) { - url = url.slice(idx + 1).replace(/:([^\d]+)/, '/$1') + const { + protocol, + hostname, + pathname + } = new URL(url) + + /* istanbul ignore next - URL ctor should prevent this */ + if (!protocol || !hostname) { + return null } - url = url_.parse(url) - const protocol = url.protocol === 'https:' - ? 'https:' - : 'http:' - return protocol + '//' + (url.host || '') + - url.path.replace(/\.git$/, '') - } catch (e) {} + + const proto = /(git\+)http:$/.test(protocol) ? 'http:' : 'https:' + const path = pathname.replace(/\.git$/, '') + return `${proto}//${hostname}${path}` + } catch (e) { + return null + } } + +module.exports = Object.assign(cmd, { usage, completion }) diff --git a/lib/utils/hosted-git-info-from-manifest.js b/lib/utils/hosted-git-info-from-manifest.js new file mode 100644 index 000000000..ecb7555b1 --- /dev/null +++ b/lib/utils/hosted-git-info-from-manifest.js @@ -0,0 +1,14 @@ +// given a manifest, try to get the hosted git info from it based on +// repository (if a string) or repository.url (if an object) +// returns null if it's not a valid repo, or not a known hosted repo +const hostedGitInfo = require('hosted-git-info') +module.exports = mani => { + const r = mani.repository + const rurl = !r ? null + : typeof r === 'string' ? r + : typeof r === 'object' && typeof r.url === 'string' ? r.url + : null + + // hgi returns undefined sometimes, but let's always return null here + return rurl && hostedGitInfo.fromUrl(rurl.replace(/^git\+/, '')) || null +} diff --git a/lib/utils/open-url.js b/lib/utils/open-url.js index 58227f039..a38a881b5 100644 --- a/lib/utils/open-url.js +++ b/lib/utils/open-url.js @@ -22,14 +22,12 @@ module.exports = function open (url, errMsg, cb, browser = npm.config.get('brows title: errMsg, url }, null, 2) - : `${errMsg}:\n\n${url}` + : `${errMsg}:\n ${url}\n` output(alternateMsg) } - const skipBrowser = process.argv.indexOf('--no-browser') > -1 - - if (skipBrowser) { + if (browser === false) { printAlternateMsg() return cb() } @@ -38,7 +36,8 @@ module.exports = function open (url, errMsg, cb, browser = npm.config.get('brows return cb(new Error('Invalid URL: ' + url)) } - opener(url, { command: browser }, (er) => { + const command = browser === true ? null : browser + opener(url, { command }, (er) => { if (er && er.code === 'ENOENT') { printAlternateMsg() return cb() diff --git a/test/lib/bugs.js b/test/lib/bugs.js new file mode 100644 index 000000000..79d508972 --- /dev/null +++ b/test/lib/bugs.js @@ -0,0 +1,96 @@ +const t = require('tap') + +const requireInject = require('require-inject') +const pacote = { + manifest: async (spec, options) => { + return spec === 'nobugs' ? { + name: 'nobugs', + version: '1.2.3' + } + : spec === 'bugsurl' ? { + name: 'bugsurl', + version: '1.2.3', + bugs: 'https://bugzilla.localhost/bugsurl' + } + : spec === 'bugsobj' ? { + name: 'bugsobj', + version: '1.2.3', + bugs: { url: 'https://bugzilla.localhost/bugsobj' } + } + : spec === 'bugsobj-nourl' ? { + name: 'bugsobj-nourl', + version: '1.2.3', + bugs: { no: 'url here' } + } + : spec === 'repourl' ? { + name: 'repourl', + version: '1.2.3', + repository: 'https://github.com/foo/repourl' + } + : spec === 'repoobj' ? { + name: 'repoobj', + version: '1.2.3', + repository: { url: 'https://github.com/foo/repoobj' } + } + : spec === '.' ? { + name: 'thispkg', + version: '1.2.3', + bugs: 'https://example.com' + } + : null + } +} + +// keep a tally of which urls got opened +const opened = {} +const openUrl = (url, errMsg, cb) => { + opened[url] = opened[url] || 0 + opened[url]++ + process.nextTick(cb) +} + +const bugs = requireInject('../../lib/bugs.js', { + pacote, + '../../lib/utils/open-url.js': openUrl +}) + +t.test('completion', t => { + bugs.completion({}, (er, res) => { + t.equal(er, null) + t.same(res, []) + t.end() + }) +}) + +t.test('open bugs urls', t => { + const expect = { + nobugs: 'https://www.npmjs.com/package/nobugs', + 'bugsobj-nourl': 'https://www.npmjs.com/package/bugsobj-nourl', + bugsurl: 'https://bugzilla.localhost/bugsurl', + bugsobj: 'https://bugzilla.localhost/bugsobj', + repourl: 'https://github.com/foo/repourl/issues', + repoobj: 'https://github.com/foo/repoobj/issues', + '.': 'https://example.com' + } + const keys = Object.keys(expect) + t.plan(keys.length) + keys.forEach(pkg => { + t.test(pkg, t => { + bugs([pkg], (er) => { + if (er) + throw er + t.equal(opened[expect[pkg]], 1, 'opened expected url', {opened}) + t.end() + }) + }) + }) +}) + +t.test('open default package if none specified', t => { + bugs([], (er) => { + if (er) + throw er + t.equal(opened['https://example.com'], 2, 'opened expected url', {opened}) + t.end() + }) +}) diff --git a/test/lib/docs.js b/test/lib/docs.js new file mode 100644 index 000000000..48ba9a3b5 --- /dev/null +++ b/test/lib/docs.js @@ -0,0 +1,85 @@ +const t = require('tap') + +const requireInject = require('require-inject') +const pacote = { + manifest: async (spec, options) => { + return spec === 'nodocs' ? { + name: 'nodocs', + version: '1.2.3' + } + : spec === 'docsurl' ? { + name: 'docsurl', + version: '1.2.3', + homepage: 'https://bugzilla.localhost/docsurl' + } + : spec === 'repourl' ? { + name: 'repourl', + version: '1.2.3', + repository: 'https://github.com/foo/repourl' + } + : spec === 'repoobj' ? { + name: 'repoobj', + version: '1.2.3', + repository: { url: 'https://github.com/foo/repoobj' } + } + : spec === '.' ? { + name: 'thispkg', + version: '1.2.3', + homepage: 'https://example.com' + } + : null + } +} + +// keep a tally of which urls got opened +const opened = {} +const openUrl = (url, errMsg, cb) => { + opened[url] = opened[url] || 0 + opened[url]++ + process.nextTick(cb) +} + +const docs = requireInject('../../lib/docs.js', { + pacote, + '../../lib/utils/open-url.js': openUrl +}) + +t.test('completion', t => { + docs.completion({}, (er, res) => { + t.equal(er, null) + t.same(res, []) + t.end() + }) +}) + +t.test('open docs urls', t => { + const expect = { + nodocs: 'https://www.npmjs.com/package/nodocs', + docsurl: 'https://bugzilla.localhost/docsurl', + repourl: 'https://github.com/foo/repourl#readme', + repoobj: 'https://github.com/foo/repoobj#readme', + '.': 'https://example.com' + } + const keys = Object.keys(expect) + t.plan(keys.length) + keys.forEach(pkg => { + t.test(pkg, t => { + docs([pkg], (er) => { + if (er) + throw er + const url = expect[pkg] + t.equal(opened[url], 1, url, {opened}) + t.end() + }) + }) + }) +}) + +t.test('open default package if none specified', t => { + docs([], (er) => { + if (er) + throw er + t.equal(opened['https://example.com'], 2, 'opened expected url', {opened}) + t.end() + }) +}) diff --git a/test/lib/repo.js b/test/lib/repo.js new file mode 100644 index 000000000..a9cddc4eb --- /dev/null +++ b/test/lib/repo.js @@ -0,0 +1,190 @@ +const t = require('tap') + +const requireInject = require('require-inject') +const pacote = { + manifest: async (spec, options) => { + return spec === 'norepo' ? { + name: 'norepo', + version: '1.2.3' + } + + : spec === 'repoobbj-nourl' ? { + name: 'repoobj-nourl', + repository: { no: 'url' } + } + + : spec === 'hostedgit' ? { + repository: 'git://github.com/foo/hostedgit' + } + : spec === 'hostedgitat' ? { + repository: 'git@github.com:foo/hostedgitat' + } + : spec === 'hostedssh' ? { + repository: 'ssh://git@github.com/foo/hostedssh' + } + : spec === 'hostedgitssh' ? { + repository: 'git+ssh://git@github.com/foo/hostedgitssh' + } + : spec === 'hostedgithttp' ? { + repository: 'git+http://github.com/foo/hostedgithttp' + } + : spec === 'hostedgithttps' ? { + repository: 'git+https://github.com/foo/hostedgithttps' + } + + : spec === 'hostedgitobj' ? { + repository: { url: 'git://github.com/foo/hostedgitobj' } + } + : spec === 'hostedgitatobj' ? { + repository: { url: 'git@github.com:foo/hostedgitatobj' } + } + : spec === 'hostedsshobj' ? { + repository: { url: 'ssh://git@github.com/foo/hostedsshobj' } + } + : spec === 'hostedgitsshobj' ? { + repository: { url: 'git+ssh://git@github.com/foo/hostedgitsshobj' } + } + : spec === 'hostedgithttpobj' ? { + repository: { url: 'git+http://github.com/foo/hostedgithttpobj' } + } + : spec === 'hostedgithttpsobj' ? { + repository: { url: 'git+https://github.com/foo/hostedgithttpsobj' } + } + + : spec === 'unhostedgit' ? { + repository: 'git://gothib.com/foo/unhostedgit' + } + : spec === 'unhostedgitat' ? { + repository: 'git@gothib.com:foo/unhostedgitat' + } + : spec === 'unhostedssh' ? { + repository: 'ssh://git@gothib.com/foo/unhostedssh' + } + : spec === 'unhostedgitssh' ? { + repository: 'git+ssh://git@gothib.com/foo/unhostedgitssh' + } + : spec === 'unhostedgithttp' ? { + repository: 'git+http://gothib.com/foo/unhostedgithttp' + } + : spec === 'unhostedgithttps' ? { + repository: 'git+https://gothib.com/foo/unhostedgithttps' + } + + : spec === 'unhostedgitobj' ? { + repository: { url: 'git://gothib.com/foo/unhostedgitobj' } + } + : spec === 'unhostedgitatobj' ? { + repository: { url: 'git@gothib.com:foo/unhostedgitatobj' } + } + : spec === 'unhostedsshobj' ? { + repository: { url: 'ssh://git@gothib.com/foo/unhostedsshobj' } + } + : spec === 'unhostedgitsshobj' ? { + repository: { url: 'git+ssh://git@gothib.com/foo/unhostedgitsshobj' } + } + : spec === 'unhostedgithttpobj' ? { + repository: { url: 'git+http://gothib.com/foo/unhostedgithttpobj' } + } + : spec === 'unhostedgithttpsobj' ? { + repository: { url: 'git+https://gothib.com/foo/unhostedgithttpsobj' } + } + + : spec === '.' ? { + name: 'thispkg', + version: '1.2.3', + repository: 'https://example.com/thispkg.git' + } + : null + } +} + +// keep a tally of which urls got opened +const opened = {} +const openUrl = (url, errMsg, cb) => { + opened[url] = opened[url] || 0 + opened[url]++ + process.nextTick(cb) +} + +const repo = requireInject('../../lib/repo.js', { + pacote, + '../../lib/utils/open-url.js': openUrl +}) + +t.test('completion', t => { + repo.completion({}, (er, res) => { + t.equal(er, null) + t.same(res, []) + t.end() + }) +}) + +t.test('open repo urls', t => { + const expect = { + hostedgit: 'https://github.com/foo/hostedgit', + hostedgitat: 'https://github.com/foo/hostedgitat', + hostedssh: 'https://github.com/foo/hostedssh', + hostedgitssh: 'https://github.com/foo/hostedgitssh', + hostedgithttp: 'https://github.com/foo/hostedgithttp', + hostedgithttps: 'https://github.com/foo/hostedgithttps', + hostedgitobj: 'https://github.com/foo/hostedgitobj', + hostedgitatobj: 'https://github.com/foo/hostedgitatobj', + hostedsshobj: 'https://github.com/foo/hostedsshobj', + hostedgitsshobj: 'https://github.com/foo/hostedgitsshobj', + hostedgithttpobj: 'https://github.com/foo/hostedgithttpobj', + hostedgithttpsobj: 'https://github.com/foo/hostedgithttpsobj', + unhostedgit: 'https://gothib.com/foo/unhostedgit', + unhostedssh: 'https://gothib.com/foo/unhostedssh', + unhostedgitssh: 'https://gothib.com/foo/unhostedgitssh', + unhostedgithttp: 'http://gothib.com/foo/unhostedgithttp', + unhostedgithttps: 'https://gothib.com/foo/unhostedgithttps', + unhostedgitobj: 'https://gothib.com/foo/unhostedgitobj', + unhostedsshobj: 'https://gothib.com/foo/unhostedsshobj', + unhostedgitsshobj: 'https://gothib.com/foo/unhostedgitsshobj', + unhostedgithttpobj: 'http://gothib.com/foo/unhostedgithttpobj', + unhostedgithttpsobj: 'https://gothib.com/foo/unhostedgithttpsobj', + '.': 'https://example.com/thispkg' + } + const keys = Object.keys(expect) + t.plan(keys.length) + keys.forEach(pkg => { + t.test(pkg, t => { + repo([pkg], (er) => { + if (er) + throw er + const url = expect[pkg] + t.equal(opened[url], 1, url, {opened}) + t.end() + }) + }) + }) +}) + +t.test('fail if cannot figure out repo url', t => { + const cases = [ + 'norepo', + 'repoobbj-nourl', + 'unhostedgitat', + 'unhostedgitatobj' + ] + + t.plan(cases.length) + + cases.forEach(pkg => { + t.test(pkg, t => { + repo([pkg], er => { + t.match(er, { pkgid: pkg }) + t.end() + }) + }) + }) +}) + +t.test('open default package if none specified', t => { + repo([], (er) => { + if (er) + throw er + t.equal(opened['https://example.com/thispkg'], 2, 'opened expected url', {opened}) + t.end() + }) +}) diff --git a/test/lib/utils/hosted-git-info-from-manifest.js b/test/lib/utils/hosted-git-info-from-manifest.js new file mode 100644 index 000000000..f87cb84ee --- /dev/null +++ b/test/lib/utils/hosted-git-info-from-manifest.js @@ -0,0 +1,21 @@ +const t = require('tap') +const hostedFromMani = require('../../../lib/utils/hosted-git-info-from-manifest.js') +const hostedGitInfo = require('hosted-git-info') + +t.equal(hostedFromMani({}), null) +t.equal(hostedFromMani({ repository: { no: 'url' } }), null) +t.equal(hostedFromMani({ repository: 123 }), null) +t.equal(hostedFromMani({ repository: 'not hosted anywhere' }), null) +t.equal(hostedFromMani({ repository: { url: 'not hosted anywhere' } }), null) + +t.match(hostedFromMani({ + repository: 'git+https://github.com/isaacs/abbrev-js' +}), hostedGitInfo.fromUrl('git+https://github.com/isaacs/abbrev-js')) + +t.match(hostedFromMani({ + repository: { url: 'git+https://github.com/isaacs/abbrev-js' } +}), hostedGitInfo.fromUrl('https://github.com/isaacs/abbrev-js')) + +t.match(hostedFromMani({ + repository: { url: 'git+ssh://git@github.com/isaacs/abbrev-js' } +}), hostedGitInfo.fromUrl('ssh://git@github.com/isaacs/abbrev-js')) diff --git a/test/tap/add-remote-git-file.js b/test/tap/add-remote-git-file.js index 483c6368c..17e06eaf3 100644 --- a/test/tap/add-remote-git-file.js +++ b/test/tap/add-remote-git-file.js @@ -1,48 +1,60 @@ 'use strict' -var fs = require('fs') -var resolve = require('path').resolve -var url = require('url') +const pacote = require('pacote') +const fs = require('fs') +const resolve = require('path').resolve +const url = require('url') -var mkdirp = require('mkdirp') -var test = require('tap').test +const mkdirp = require('mkdirp') +const test = require('tap').test -var npm = require('../../lib/npm.js') -var fetchPackageMetadata = require('../../lib/fetch-package-metadata.js') -var common = require('../common-tap.js') +const npm = require('../../lib/npm.js') +const common = require('../common-tap.js') -var pkg = resolve(common.pkg, 'package') -var repo = resolve(common.pkg, 'repo') +const cache = common.cache +const pkg = resolve(common.pkg, 'package') +const repo = resolve(common.pkg, 'repo') mkdirp.sync(pkg) -var git -var cloneURL = 'git+file://' + resolve(pkg, 'child.git') +let git +const cloneURL = 'git+file://' + resolve(pkg, 'child.git') -var pjChild = JSON.stringify({ +const pjChild = JSON.stringify({ name: 'child', version: '1.0.3' }, null, 2) + '\n' -test('setup', function (t) { - setup(function (er, r) { - t.ifError(er, 'git started up successfully') +test('setup', { bail: true }, function (t) { + mkdirp.sync(repo) + fs.writeFileSync(resolve(repo, 'package.json'), pjChild) + npm.load({ + cache, + registry: common.registry, + loglevel: 'silent' + }, function () { + git = require('../../lib/utils/git.js') - t.end() + common.makeGitRepo({ + path: repo, + commands: [git.chainableExec( + ['clone', '--bare', repo, 'child.git'], + { cwd: pkg, env: process.env } + )] + }, er => { + t.ifError(er) + t.end() + }) }) }) -test('cache from repo', function (t) { +test('cache from repo', async t => { process.chdir(pkg) - fetchPackageMetadata(cloneURL, process.cwd(), {}, (err, manifest) => { - if (err) t.fail(err.message) - t.equal( - url.parse(manifest._resolved).protocol, - 'git+file:', - 'npm didn\'t go crazy adding git+git+git+git' - ) - t.equal(manifest._requested.type, 'git', 'cached git') - t.end() - }) + const manifest = await pacote.manifest(cloneURL, npm.flatOptions) + t.equal( + url.parse(manifest._resolved).protocol, + 'git+file:', + 'npm didn\'t go crazy adding git+git+git+git' + ) }) test('save install', function (t) { @@ -51,13 +63,13 @@ test('save install', function (t) { name: 'parent', version: '5.4.3' }, null, 2) + '\n') - var prev = npm.config.get('save') + const prev = npm.config.get('save') npm.config.set('save', true) npm.commands.install('.', [cloneURL], function (er) { npm.config.set('save', prev) t.ifError(er, 'npm installed via git') - var pj = JSON.parse(fs.readFileSync('package.json', 'utf-8')) - var dep = pj.dependencies.child + const pj = JSON.parse(fs.readFileSync('package.json', 'utf-8')) + const dep = pj.dependencies.child t.equal( url.parse(dep).protocol, 'git+file:', @@ -67,19 +79,3 @@ test('save install', function (t) { t.end() }) }) - -function setup (cb) { - mkdirp.sync(repo) - fs.writeFileSync(resolve(repo, 'package.json'), pjChild) - npm.load({ registry: common.registry, loglevel: 'silent' }, function () { - git = require('../../lib/utils/git.js') - - common.makeGitRepo({ - path: repo, - commands: [git.chainableExec( - ['clone', '--bare', repo, 'child.git'], - { cwd: pkg, env: process.env } - )] - }, cb) - }) -} diff --git a/test/tap/bugs.js b/test/tap/bugs.js deleted file mode 100644 index 8e2685220..000000000 --- a/test/tap/bugs.js +++ /dev/null @@ -1,172 +0,0 @@ -var common = require('../common-tap.js') -common.pendIfWindows('not working because Windows and shebangs') - -var mr = require('npm-registry-mock') - -var test = require('tap').test -var rimraf = require('rimraf') -var fs = require('fs') -var path = require('path') -var join = path.join -var outFile = path.join(common.pkg, '/_output') - -var opts = { cwd: common.pkg } - -test('setup', function (t) { - var s = '#!/usr/bin/env bash\n' + - 'echo "$@" > ' + JSON.stringify(common.pkg) + '/_output\n' - fs.writeFileSync(join(common.pkg, '/_script.sh'), s, 'ascii') - fs.chmodSync(join(common.pkg, '/_script.sh'), '0755') - t.pass('made script') - t.end() -}) - -test('npm bugs underscore', function (t) { - mr({ port: common.port }, function (er, s) { - common.npm( - [ - 'bugs', 'underscore', - '--registry=' + common.registry, - '--loglevel=silent', - '--browser=' + join(common.pkg, '/_script.sh') - ], - opts, - function (err, code, stdout, stderr) { - t.ifError(err, 'bugs ran without issue') - t.notOk(stderr, 'should have no stderr') - t.equal(code, 0, 'exit ok') - var res = fs.readFileSync(outFile, 'ascii') - s.close() - t.equal(res, 'https://github.com/jashkenas/underscore/issues\n') - rimraf.sync(outFile) - t.end() - } - ) - }) -}) - -test('npm bugs optimist - github (https://)', function (t) { - mr({ port: common.port }, function (er, s) { - common.npm( - [ - 'bugs', 'optimist', - '--registry=' + common.registry, - '--loglevel=silent', - '--browser=' + join(common.pkg, '/_script.sh') - ], - opts, - function (err, code, stdout, stderr) { - t.ifError(err, 'bugs ran without issue') - t.notOk(stderr, 'should have no stderr') - t.equal(code, 0, 'exit ok') - var res = fs.readFileSync(outFile, 'ascii') - s.close() - t.equal(res, 'https://github.com/substack/node-optimist/issues\n') - rimraf.sync(outFile) - t.end() - } - ) - }) -}) - -test('npm bugs npm-test-peer-deps - no repo', function (t) { - mr({ port: common.port }, function (er, s) { - common.npm( - [ - 'bugs', 'npm-test-peer-deps', - '--registry=' + common.registry, - '--loglevel=silent', - '--browser=' + join(common.pkg, '/_script.sh') - ], - opts, - function (err, code, stdout, stderr) { - t.ifError(err, 'bugs ran without issue') - t.notOk(stderr, 'should have no stderr') - t.equal(code, 0, 'exit ok') - var res = fs.readFileSync(outFile, 'ascii') - s.close() - t.equal(res, 'https://www.npmjs.org/package/npm-test-peer-deps\n') - rimraf.sync(outFile) - t.end() - } - ) - }) -}) - -test('npm bugs test-repo-url-http - non-github (http://)', function (t) { - mr({ port: common.port }, function (er, s) { - common.npm( - [ - 'bugs', 'test-repo-url-http', - '--registry=' + common.registry, - '--loglevel=silent', - '--browser=' + join(common.pkg, '/_script.sh') - ], - opts, - function (err, code, stdout, stderr) { - t.ifError(err, 'bugs ran without issue') - t.notOk(stderr, 'should have no stderr') - t.equal(code, 0, 'exit ok') - var res = fs.readFileSync(outFile, 'ascii') - s.close() - t.equal(res, 'https://www.npmjs.org/package/test-repo-url-http\n') - rimraf.sync(outFile) - t.end() - } - ) - }) -}) - -test('npm bugs test-repo-url-https - gitlab (https://)', function (t) { - mr({ port: common.port }, function (er, s) { - common.npm( - [ - 'bugs', 'test-repo-url-https', - '--registry=' + common.registry, - '--loglevel=silent', - '--browser=' + join(common.pkg, '/_script.sh') - ], - opts, - function (err, code, stdout, stderr) { - t.ifError(err, 'bugs ran without issue') - t.notOk(stderr, 'should have no stderr') - t.equal(code, 0, 'exit ok') - var res = fs.readFileSync(outFile, 'ascii') - s.close() - t.equal(res, 'https://gitlab.com/evanlucas/test-repo-url-https/issues\n') - rimraf.sync(outFile) - t.end() - } - ) - }) -}) - -test('npm bugs test-repo-url-ssh - gitlab (ssh://)', function (t) { - mr({ port: common.port }, function (er, s) { - common.npm( - [ - 'bugs', 'test-repo-url-ssh', - '--registry=' + common.registry, - '--loglevel=silent', - '--browser=' + join(common.pkg, '/_script.sh') - ], - opts, - function (err, code, stdout, stderr) { - t.ifError(err, 'bugs ran without issue') - t.notOk(stderr, 'should have no stderr') - t.equal(code, 0, 'exit ok') - var res = fs.readFileSync(outFile, 'ascii') - s.close() - t.equal(res, 'https://gitlab.com/evanlucas/test-repo-url-ssh/issues\n') - rimraf.sync(outFile) - t.end() - } - ) - }) -}) - -test('cleanup', function (t) { - rimraf.sync(common.pkg) - t.pass('cleaned up') - t.end() -}) diff --git a/test/tap/fetch-package-metadata.js b/test/tap/fetch-package-metadata.js deleted file mode 100644 index 2e666772e..000000000 --- a/test/tap/fetch-package-metadata.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict' -var mr = require('npm-registry-mock') -var npa = require('npm-package-arg') -var test = require('tap').test - -var common = require('../common-tap.js') -var npm = require('../../lib/npm.js') - -var pkg = common.pkg - -test('setup', function (t) { - process.chdir(pkg) - - var opts = { - cache: common.cache, - registry: common.registry, - // important to make sure devDependencies don't get stripped - dev: true - } - npm.load(opts, t.end) -}) - -test('fetch-package-metadata provides resolved metadata', function (t) { - t.plan(4) - - var fetchPackageMetadata = require('../../lib/fetch-package-metadata') - - var testPackage = npa('test-package@>=0.0.0') - - mr({ port: common.port }, thenFetchMetadata) - - var server - function thenFetchMetadata (err, s) { - t.ifError(err, 'setup mock registry') - server = s - - fetchPackageMetadata(testPackage, __dirname, thenVerifyMetadata) - } - - function thenVerifyMetadata (err, pkg) { - t.ifError(err, 'fetched metadata') - - t.equals(pkg._resolved, 'http://localhost:' + common.port + '/test-package/-/test-package-0.0.0.tgz', '_resolved') - t.equals(pkg._integrity, 'sha1-sNMrbEXCWcV4uiADdisgUTG9+9E=', '_integrity') - server.close() - t.end() - } -}) |