UNPKG

5.81 kBJavaScriptView Raw
1'use strict';
2
3var debug = require('debug')('npmjs::packages')
4 , normalize = require('../normalize')
5 , licenses = require('licenses')
6 , semver = require('../semver');
7
8/**
9 * Get all package information.
10 *
11 * @constructor
12 * @param {Registry} api Reference to the wrapping registry.
13 * @api private
14 */
15function Packages(api) {
16 this.api = api;
17
18 this.send = api.send.bind(api);
19 this.view = api.view.bind(api);
20}
21
22/**
23 * Get information from the npm package. If the name contains an `@` char we
24 * assume that the user wants to get a specific version instead.
25 * Example:
26 *
27 * - primus@0.1.1 would retreive primus version 0.1.1
28 *
29 * @param {String} name The name of the node module.
30 * @param {Function} fn The callback.
31 * @returns {Assign}
32 * @api public
33 */
34Packages.prototype.get = function get(name, fn) {
35 return this.send(name.replace('@', '/'), fn).map(normalize.packages);
36};
37
38/**
39 * Get all packages that are depended upon a given package name.
40 *
41 * @param {String} name The name of the node module.
42 * @param {Function} fn The callback
43 * @returns {Assign}
44 * @api public
45 */
46Packages.prototype.depended = function depended(name, fn) {
47 return this.view('dependedUpon', {
48 key: name
49 }, fn).map(this.api.map.simple);
50};
51
52/**
53 * Find out which users have starred the given package.
54 *
55 * @param {String} name The name of the node module.
56 * @param {Function} fn The callback
57 * @returns {Assign}
58 * @api public
59 */
60Packages.prototype.starred = function starred(name, fn) {
61 return this.view('browseStarPackage', {
62 key: name
63 }, fn).map(function map(data) {
64 return data[2];
65 });
66};
67
68/**
69 * Find all packages that matches the giving keywords.
70 *
71 * @param {String} name The keyword.
72 * @param {Function} fn The callback.
73 * @returns {Assign}
74 * @api public
75 */
76Packages.prototype.keyword = function keyword(name, fn) {
77 return this.view('byKeyword', {
78 key: name
79 }, fn).map(this.api.map.simple);
80};
81
82/**
83 * Retrieve all release specific information for the given package name.
84 *
85 * @param {String} name The package name.
86 * @param {Function} fn The callback.
87 * @api public
88 */
89Packages.prototype.releases = function releases(name, fn) {
90 var api = this.api;
91
92 return this.details(name, fn).emits(function emit(data, add) {
93 if (!data.versions) return;
94
95 //
96 // Add all versions of the given module.
97 //
98 Object.keys(data.versions).forEach(function addmore(version) {
99 var release = data.versions[version];
100 release.date = data.time[version];
101
102 add(normalize.packages(release, data));
103 });
104
105 //
106 // Also add each tag to the releases.
107 //
108 if ('dist-tags' in data) Object.keys(data['dist-tags']).forEach(function (key) {
109 if (key in data.versions) return; // Prevent duplicates
110
111 var version = data['dist-tags'][key]
112 , release;
113
114 //
115 // It's possible that the tag does not exist in the versions object. This
116 // is some odd npm edge case.
117 //
118 // Lesson learned: Never trust npm data structures.
119 //
120 if (!version || !(version in data.versions)) return;
121
122 release = api.merge({}, data.versions[version]);
123
124 //
125 // The JSON.parse(JSON.stringify)) is needed to create a full clone of the
126 // data structure as we're adding tags. That would be override during the
127 // `reduce` procedure.
128 //
129 release.date = data.time[version];
130 release.tag = key;
131
132 add(normalize.packages(release, data));
133 });
134
135 return false;
136 }).reduce(function reduce(memo, release) {
137 memo[release.tag || release.version] = release;
138 return memo;
139 }, {});
140};
141
142/**
143 * Get a specific release of a package.
144 *
145 * @param {String} name The name of the package
146 * @param {String} version A valid version number or tag from the package.
147 * @param {Function} fn The callback
148 * @returns {Assign} Assignment
149 * @api public
150 */
151Packages.prototype.release = function release(name, version, fn) {
152 return this.details(name +'/'+ version, fn).map(normalize.packages);
153};
154
155/**
156 * Get a version for a specific release.
157 *
158 * @param {String} name The name of the package.
159 * @param {String} range The semver version range we should retrieve.
160 * @param {Function} fn The callback
161 * @returns {Assign} Assignment
162 * @api public
163 */
164Packages.prototype.range = function ranged(name, range, fn) {
165 if (!semver.validRange(range)) return fn(new Error('Invalid semver range'));
166
167 return this.releases(name, function releases(err, versions) {
168 if (err) return fn(err);
169
170 if (range in versions) {
171 debug('found and direct range (%s) match for %s', range, name);
172 return fn(undefined, versions[range]);
173 }
174
175 var version = semver.maxSatisfying(Object.keys(versions), range);
176
177 debug('max satisfying version for %s is %s', name, version);
178 fn(undefined, versions[version]);
179 });
180};
181
182/**
183 * Retrieve additional details for the package information. This a lot slower
184 * than a simple `.get` but much more detailed and accurate as it uses custom
185 * parsers and mapping operations to parse the data as good as possible.
186 *
187 * @TODO Extract missing descriptions from github.
188 * @TODO Merge profile information from github / authors.
189 *
190 * @param {String} name The name of the node module.
191 * @param {Function} fn The callback.
192 * @returns {Assign}
193 * @api public
194 */
195Packages.prototype.details = function details(name, fn) {
196 var packages = this;
197
198 return this.get(name, fn).async.map(function map(data, next) {
199 licenses(data, {
200 githulk: packages.api.githulk,
201 npmjs: packages
202 }, function parsed(err, licenses) {
203 data.licenses = licenses;
204
205 if (err) debug('failed to detect license: %s', err.message);
206 return next(err, data);
207 });
208 });
209};
210
211//
212// Expose the module.
213//
214module.exports = Packages;