UNPKG

6.63 kBJavaScriptView Raw
1'use strict';
2
3var creation = '2010-01-14T01:41:08-08:00' // The date that the registry got spec'd.
4 , extract = require('extract-github')
5 , semver = require('../semver')
6 , to = require('./to');
7
8/**
9 * Normalize package data.
10 *
11 * @param {Object} data The package data.
12 * @param {Object} fallback Optional data structure to fallback
13 * @returns {Object} The cleaned up data structure.
14 * @api public
15 */
16function packages(data, fallback) {
17 if (!data || 'object' !== to.type(data)) return {};
18
19 var releases = Object.keys(data.versions || data.times || {})
20 , latest;
21
22 releases = releases.filter(function clean(version) {
23 try { return !!semver.valid(version, true); }
24 catch (e) { return false; }
25 }).sort(function sort(a, b) {
26 return semver.gt(a, b) ? -1 : 1;
27 }).reduce(function reduce(result, release) {
28 result[release] = data.versions[release]._npmUser;
29 return result;
30 }, {});
31
32 //
33 // Clean up the dist-tags before we can figure out the latest package.
34 //
35 if ('object' !== typeof data['dist-tags']) data['dist-tags'] = {};
36 if (!('latest' in data['dist-tags'])) data['dist-tags'].latest = releases[0];
37
38 latest = (data.versions || {})[data['dist-tags'].latest] || {};
39
40 if (to.type(fallback) !== 'object') {
41 fallback = latest;
42
43 //
44 // The fastest way of creating a clone of an object.
45 //
46 try { fallback = JSON.parse(JSON.stringify(fallback)); }
47 catch (e) {}
48 }
49
50 //
51 // These can not be transformed to a normal value that easily so we set them
52 // first.
53 //
54 data._id = data.name = data.name || data._id || fallback.name || fallback._id;
55 data.license = fallback.license || data.license;
56 data.licenses = to.licenses(data, fallback);
57 data.github = extract(data);
58 data.releases = releases;
59 data.latest = latest;
60
61 [
62 { key: '_npmUser', value: {}, parse: to.gravatar },
63 { key: 'bugs', value: {}, parse: to.github('issues') },
64 { key: 'bundledDependencies', value: [] },
65 { key: 'dependencies', value: {} },
66 { key: 'description', value: '' },
67 { key: 'devDependencies', value: {} },
68 { key: 'engines', value: {} },
69 { key: 'homepage', value: {}, parse: to.github() },
70 { key: 'keywords', value: [] },
71 { key: 'maintainers', value: [], parse: to.gravatar },
72 { key: 'optionalDependencies', value: {} },
73 { key: 'peerDependencies', value: {} },
74 { key: 'readme', value: '' },
75 { key: 'readmeFilename', value: '' },
76 { key: 'repository', value: {}, parse: to.github() },
77 { key: 'scripts', value: {} },
78 { key: 'time', value: {} },
79 { key: 'version', value: '' },
80 { key: 'versions', value: {} }
81 ].forEach(function each(transform) {
82 var key = transform.key;
83
84 data[key] = fallback[key] || data[key] || transform.value;
85
86 //
87 // If there's an additional data transformer run that over the structure.
88 //
89 if (transform.parse && data[key]) {
90 if (Array.isArray(data[key])) {
91 data[key] = data[key].map(transform.parse.bind(data));
92 } else {
93 data[key] = transform.parse.call(data, data[key]);
94 }
95 }
96
97 //
98 // Additional check to ensure that the field has the correct value. Or we
99 // will default to our normal value.
100 //
101 if (to.type(data[key]) !== to.type(transform.value)) {
102 data[key] = transform.value;
103 }
104 });
105
106 //
107 // Transform keywords in to an array.
108 //
109 if ('string' === typeof data.keywords) data.keywords.split(/[\s|,]{1,}/);
110 if (!Array.isArray(data.keywords)) delete data.keywords;
111
112 //
113 // Add modification and creation as real date objects to the data structure.
114 // They are hidden in a `time` object.
115 //
116 if (!data.modified || !data.created) {
117 data.modified = data.modified || data.mtime;
118 data.created = data.created || data.ctime;
119
120 if (data.time.modified && !data.modified) data.modified = data.time.modified;
121 if (data.time.created && !data.created) data.created = data.time.created;
122
123 if (!data.modified && releases[0] in data.time) {
124 data.modified = data.time[releases[0]];
125 }
126
127 if (!data.created && releases[releases.length -1] in data.time) {
128 data.created = data.time[releases[releases.length -1]];
129 }
130
131 data.modified = data.modified || creation;
132 data.created = data.created || creation;
133 }
134
135 //
136 // Transform all dates to valid Date instances.
137 //
138 if ('string' === typeof data.modified) data.modified = new Date(data.modified);
139 if ('string' === typeof data.created) data.created = new Date(data.created);
140
141 Object.keys(data.time).forEach(function normalize(version) {
142 data.time[version] = new Date(data.time[version]);
143 });
144
145 //
146 // data.users is actually the people who've starred this module using npm.star
147 // nobody in their right minds would have known that if you know what you're
148 // looking for.
149 //
150 data.starred = Object.keys(data.users || fallback.users || {});
151
152 //
153 // Clean up the data structure with information that is not needed or is
154 // pointlessly recursive. Or should not be defined if it's empty.
155 //
156 if (!data.readmeFilename) delete data.readmeFile;
157 if (data._attachments) delete data._attachments;
158
159 //
160 // Another npm oddety, if you don't have a README file it will just add `no
161 // README data found` as content instead of actually solving this at the view
162 // level of a website.
163 //
164 if (!data.readme || /no readme data found/i.test(data.readme)) delete data.readme;
165
166 //
167 // It could be that a given module has been (forcefully) unpublished by the
168 // registry.
169 //
170 data.unpublished = data._deleted === true || !!data.time.unpublished;
171
172 // @TODO reuse github information for missing bugs fields.
173 // @TODO normalize .web / .url in repo, license author etc.
174 // @TODO reuse github for homepage.
175 return data;
176}
177
178//
179// Expose the normalizer
180//
181module.exports = packages;