1 | var request = require('request');
|
2 | var check = require('check-types');
|
3 | var verify = check.verify;
|
4 | var semver = require('semver');
|
5 | var q = require('q');
|
6 | var localVersion = require('./local-module-version');
|
7 | var isUrl = require('npm-utils').isUrl;
|
8 | var _ = require('lodash');
|
9 |
|
10 | var _registryUrl = require('npm-utils').registryUrl;
|
11 | check.verify.fn(_registryUrl, 'expected registry url function');
|
12 | var registryUrl = _.once(_registryUrl);
|
13 |
|
14 | function cleanVersion(version, name) {
|
15 | var originalVersion = version;
|
16 | verify.unemptyString(version, 'missing version string' + version);
|
17 | verify.unemptyString(name, 'missing name string' + name);
|
18 |
|
19 | if (isUrl(version)) {
|
20 |
|
21 |
|
22 |
|
23 |
|
24 | console.log('Cannot handle git repos, skipping', name, 'at', version);
|
25 | return;
|
26 | }
|
27 | if (version === 'original' ||
|
28 | version === 'modified' ||
|
29 | version === 'created') {
|
30 | return;
|
31 | }
|
32 |
|
33 | version = version.replace('~', '').replace('^', '');
|
34 | var twoDigitVersion = /^\d+\.\d+$/;
|
35 | if (twoDigitVersion.test(version)) {
|
36 | version += '.0';
|
37 | }
|
38 | if (version === 'latest' || version === '*') {
|
39 | console.log('Module', name, 'uses version', version);
|
40 | console.log('It is recommented to list a specific version number');
|
41 | version = localVersion(name);
|
42 | if (!version) {
|
43 | version = '0.0.1';
|
44 | }
|
45 | console.log('module', name, 'local version', version);
|
46 | }
|
47 | try {
|
48 | version = semver.clean(version);
|
49 | } catch (err) {
|
50 | console.error('exception when cleaning version', version);
|
51 | return;
|
52 | }
|
53 | if (!version) {
|
54 | console.error('could not clean version ' + originalVersion + ' for ' + name);
|
55 | return;
|
56 | }
|
57 | console.assert(version, 'missing clean version ' + originalVersion + ' for ' + name);
|
58 | return version;
|
59 | }
|
60 |
|
61 | function cleanVersionPair(nameVersion) {
|
62 | check.verify.array(nameVersion, 'expected an array');
|
63 | console.assert(nameVersion.length === 2,
|
64 | 'expected 2 items, name and version ' + nameVersion);
|
65 | var name = nameVersion[0];
|
66 | check.verify.string(name, 'could not get module name from ' + nameVersion);
|
67 |
|
68 | var version = nameVersion[1];
|
69 | check.verify.string(version, 'could not get module version from ' + nameVersion);
|
70 | version = cleanVersion(version, name);
|
71 | if (!version) {
|
72 | return;
|
73 | }
|
74 |
|
75 | nameVersion[1] = version;
|
76 | return nameVersion;
|
77 | }
|
78 |
|
79 | function cleanVersionObject(info) {
|
80 | check.verify.object(info, 'expected info');
|
81 | var name = info.name;
|
82 | check.verify.string(name, 'could not get module name from ' + info);
|
83 |
|
84 | var version = info.version;
|
85 | check.verify.string(version, 'could not get module version from ' + info);
|
86 | version = cleanVersion(version, name);
|
87 |
|
88 | if (!version) {
|
89 | return;
|
90 | }
|
91 |
|
92 | info.version = version;
|
93 | return info;
|
94 | }
|
95 |
|
96 | function cleanVersions(nameVersionPairs) {
|
97 | check.verify.array(nameVersionPairs, 'expected array');
|
98 | var cleaned = nameVersionPairs
|
99 | .map(cleanVersionObject)
|
100 | .filter(check.object);
|
101 | return cleaned;
|
102 | }
|
103 |
|
104 |
|
105 |
|
106 |
|
107 | function fetchVersions(nameVersion) {
|
108 |
|
109 |
|
110 | check.verify.object(nameVersion, 'expected name, version object');
|
111 | var name = nameVersion.name;
|
112 | var version = nameVersion.version;
|
113 | check.verify.string(name, 'missing name string');
|
114 | check.verify.string(version, 'missing version string');
|
115 |
|
116 |
|
117 | var MAX_WAIT_TIMEOUT = 5000;
|
118 | var deferred = q.defer();
|
119 |
|
120 | registryUrl().then(function (npmUrl) {
|
121 | check.verify.webUrl(npmUrl, 'need npm registry url, got ' + npmUrl);
|
122 |
|
123 | npmUrl = npmUrl.replace(/^https:/, 'http:').trim();
|
124 | var url = npmUrl + name;
|
125 |
|
126 |
|
127 |
|
128 | request.get(url, onNPMversions);
|
129 | var timer = setTimeout(function () {
|
130 | var msg = 'timed out waiting for NPM for package ' + name;
|
131 | console.error(msg);
|
132 | deferred.reject(msg);
|
133 | }, MAX_WAIT_TIMEOUT);
|
134 |
|
135 | function onNPMversions(err, response, body) {
|
136 | clearTimeout(timer);
|
137 |
|
138 | if (err) {
|
139 | console.error('ERROR when fetching info for package', name);
|
140 | deferred.reject(err.message);
|
141 | return;
|
142 | }
|
143 |
|
144 | try {
|
145 | var info = JSON.parse(body);
|
146 | if (info.error) {
|
147 | var str = 'ERROR in npm info for ' + name + ' reason ' + info.reason;
|
148 | console.error(str);
|
149 | deferred.reject(str);
|
150 | return;
|
151 | }
|
152 | var versions;
|
153 | if (info.time) {
|
154 | versions = Object.keys(info.time);
|
155 | } else if (info.versions) {
|
156 | versions = Object.keys(info.versions);
|
157 | }
|
158 | if (!Array.isArray(versions)) {
|
159 | throw new Error('Could not get versions for ' + name + ' from ' + info);
|
160 | }
|
161 |
|
162 | var validVersions = versions.filter(function (version) {
|
163 | return cleanVersion(version, name);
|
164 | });
|
165 | var newerVersions = validVersions.filter(function (ver) {
|
166 | var later = semver.gt(ver, version);
|
167 | return later;
|
168 | });
|
169 |
|
170 | deferred.resolve({
|
171 | name: name,
|
172 | versions: newerVersions
|
173 | });
|
174 | return;
|
175 | } catch (err) {
|
176 | console.error(err);
|
177 | deferred.reject('Could not fetch versions for ' + name);
|
178 | return;
|
179 | }
|
180 | }
|
181 | });
|
182 |
|
183 | return deferred.promise;
|
184 | }
|
185 |
|
186 |
|
187 | function nextVersions(options, nameVersionPairs, checkLatestOnly) {
|
188 | check.verify.object(options, 'expected object with options');
|
189 | check.verify.array(nameVersionPairs, 'expected array');
|
190 | checkLatestOnly = !!checkLatestOnly;
|
191 | nameVersionPairs = cleanVersions(nameVersionPairs);
|
192 |
|
193 | var MAX_CHECK_TIMEOUT = 10000;
|
194 | var deferred = q.defer();
|
195 |
|
196 | if (!options.tldr) {
|
197 | console.log('checking NPM registry');
|
198 | }
|
199 | var fetchPromises = nameVersionPairs.map(fetchVersions);
|
200 | var fetchAllPromise = q.all(fetchPromises)
|
201 | .timeout(MAX_CHECK_TIMEOUT, 'timed out waiting for NPM');
|
202 |
|
203 | fetchAllPromise.then(function (results) {
|
204 | var available = results.filter(function (nameNewVersions) {
|
205 | return nameNewVersions.versions.length;
|
206 | });
|
207 | if (checkLatestOnly) {
|
208 | available = available.map(function (nameVersions) {
|
209 | if (nameVersions.versions.length > 1) {
|
210 | nameVersions.versions = nameVersions.versions.slice(-1);
|
211 | }
|
212 | return nameVersions;
|
213 | });
|
214 | } else {
|
215 | console.log('checking ALL versions');
|
216 | }
|
217 | deferred.resolve(available);
|
218 | }, function (error) {
|
219 | deferred.reject(error);
|
220 | });
|
221 |
|
222 | return deferred.promise;
|
223 | }
|
224 |
|
225 | module.exports = {
|
226 | isUrl: isUrl,
|
227 | cleanVersion: cleanVersion,
|
228 | cleanVersions: cleanVersions,
|
229 | fetchVersions: fetchVersions,
|
230 | nextVersions: nextVersions
|
231 | };
|