diff --git a/lib/storage.js b/lib/storage.js index 6c73260ed..5b5aa0c5b 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -364,9 +364,11 @@ class Storage { this._sync_package_with_uplinks(name, data, options, function(err, result, uplink_errors) { if (err) return callback(err); - const whitelist = ['_rev', 'name', 'versions', 'dist-tags', 'readme']; + const whitelist = ['_rev', 'name', 'versions', 'dist-tags', 'readme', 'time']; for (let i in result) { - if (whitelist.indexOf(i) === -1) delete result[i]; + if (whitelist.indexOf(i) === -1) { + delete result[i]; + } } Utils.normalize_dist_tags(result); @@ -477,17 +479,17 @@ class Storage { if package is available locally, it MUST be provided in pkginfo returns callback(err, result, uplink_errors) * @param {*} name - * @param {*} pkginfo + * @param {*} packageInfo * @param {*} options * @param {*} callback */ - _sync_package_with_uplinks(name, pkginfo, options, callback) { + _sync_package_with_uplinks(name, packageInfo, options, callback) { let self = this; let exists = false; - if (!pkginfo) { + if (!packageInfo) { exists = false; - pkginfo = { + packageInfo = { 'name': name, 'versions': {}, 'dist-tags': {}, @@ -497,35 +499,35 @@ class Storage { exists = true; } - let uplinks = []; + let upLinks = []; for (let i in self.uplinks) { if (self.config.hasProxyTo(name, i)) { - uplinks.push(self.uplinks[i]); + upLinks.push(self.uplinks[i]); } } - async.map(uplinks, function(up, cb) { + async.map(upLinks, function(up, cb) { let _options = Object.assign({}, options); - if (Utils.is_object(pkginfo._uplinks[up.upname])) { - let fetched = pkginfo._uplinks[up.upname].fetched; + if (Utils.is_object(packageInfo._uplinks[up.upname])) { + let fetched = packageInfo._uplinks[up.upname].fetched; if (fetched && fetched > (Date.now() - up.maxage)) { return cb(); } - _options.etag = pkginfo._uplinks[up.upname].etag; + _options.etag = packageInfo._uplinks[up.upname].etag; } - up.getRemotePackage(name, _options, function(err, up_res, etag) { + up.getRemotePackage(name, _options, function(err, upLinkResponse, etag) { if (err && err.status === 304) { - pkginfo._uplinks[up.upname].fetched = Date.now(); + packageInfo._uplinks[up.upname].fetched = Date.now(); } - if (err || !up_res) { + if (err || !upLinkResponse) { return cb(null, [err || Error('no data')]); } try { - Utils.validate_metadata(up_res, name); + Utils.validate_metadata(upLinkResponse, name); } catch(err) { self.logger.error({ sub: 'out', @@ -534,15 +536,21 @@ class Storage { return cb(null, [err]); } - pkginfo._uplinks[up.upname] = { + packageInfo._uplinks[up.upname] = { etag: etag, fetched: Date.now(), }; - for (let i in up_res.versions) { - if (Object.prototype.hasOwnProperty.call(up_res.versions, i)) { + + // added to fix verdaccio#73 + if ('time' in upLinkResponse) { + packageInfo['time'] = upLinkResponse.time; + } + + for (let i in upLinkResponse.versions) { + if (Object.prototype.hasOwnProperty.call(upLinkResponse.versions, i)) { // this won't be serialized to json, // kinda like an ES6 Symbol - Object.defineProperty(up_res.versions[i], '_verdaccio_uplink', { + Object.defineProperty(upLinkResponse.versions[i], '_verdaccio_uplink', { value: up.upname, enumerable: false, configurable: false, @@ -552,7 +560,7 @@ class Storage { } try { - Storage._merge_versions(pkginfo, up_res, self.config); + Storage._merge_versions(packageInfo, upLinkResponse, self.config); } catch(err) { self.logger.error({ sub: 'out', @@ -566,18 +574,20 @@ class Storage { exists = true; cb(); }); - }, function(err, uplink_errors) { - assert(!err && Array.isArray(uplink_errors)); + }, function(err, upLinksErrors) { + assert(!err && Array.isArray(upLinksErrors)); if (!exists) { return callback( Error[404]('no such package available') , null - , uplink_errors ); + , upLinksErrors ); } - self.localStorage.updateVersions(name, pkginfo, function(err, pkginfo) { - if (err) return callback(err); - return callback(null, pkginfo, uplink_errors); + self.localStorage.updateVersions(name, packageInfo, function(err, packageJsonLocal) { + if (err) { + return callback(err); + } + return callback(null, packageJsonLocal, upLinksErrors); }); }); } diff --git a/lib/storage/local/local-storage.js b/lib/storage/local/local-storage.js index 3c56555de..f7ce5fda1 100644 --- a/lib/storage/local/local-storage.js +++ b/lib/storage/local/local-storage.js @@ -7,7 +7,7 @@ const Crypto = require('crypto'); const fs = require('fs'); const Path = require('path'); const Stream = require('stream'); -const url = require('url'); +const URL = require('url'); const async = require('async'); const createError = require('http-errors'); const _ = require('lodash'); @@ -29,6 +29,7 @@ const generatePackageTemplate = function(name) { 'name': name, 'versions': {}, 'dist-tags': {}, + 'time': {}, // our own object '_distfiles': {}, @@ -148,83 +149,88 @@ class LocalStorage { /** * Synchronize remote package info with the local one * @param {*} name - * @param {*} newdata + * @param {*} packageInfo * @param {*} callback */ - updateVersions(name, newdata, callback) { - this._readCreatePackage(name, (err, data) => { + updateVersions(name, packageInfo, callback) { + this._readCreatePackage(name, (err, packageLocalJson) => { if (err) { return callback(err); } let change = false; - for (let ver in newdata.versions) { - if (_.isNil(data.versions[ver])) { - let verdata = newdata.versions[ver]; + for (let versionId in packageInfo.versions) { + if (_.isNil(packageLocalJson.versions[versionId])) { + const version = packageInfo.versions[versionId]; // we don't keep readmes for package versions, // only one readme per package - delete verdata.readme; + delete version.readme; change = true; - data.versions[ver] = verdata; + packageLocalJson.versions[versionId] = version; - if (verdata.dist && verdata.dist.tarball) { - let filename = url.parse(verdata.dist.tarball).pathname.replace(/^.*\//, ''); + if (version.dist && version.dist.tarball) { + let filename = URL.parse(version.dist.tarball).pathname.replace(/^.*\//, ''); // we do NOT overwrite any existing records - if (_.isNil(data._distfiles[filename])) { - let hash = data._distfiles[filename] = { - url: verdata.dist.tarball, - sha: verdata.dist.shasum, + if (_.isNil(packageLocalJson._distfiles[filename])) { + let hash = packageLocalJson._distfiles[filename] = { + url: version.dist.tarball, + sha: version.dist.shasum, }; // if (verdata[Symbol('_verdaccio_uplink')]) { - if (verdata._verdaccio_uplink) { + if (version._verdaccio_uplink) { // if we got this information from a known registry, // use the same protocol for the tarball // // see https://github.com/rlidwka/sinopia/issues/166 - const tarball_url = url.parse(hash.url); - const uplink_url = url.parse(this.config.uplinks[verdata._verdaccio_uplink].url); - if (uplink_url.host === tarball_url.host) { - tarball_url.protocol = uplink_url.protocol; - hash.registry = verdata._verdaccio_uplink; - hash.url = url.format(tarball_url); + const tarballUrl = URL.parse(hash.url); + const uplinkUrl = URL.parse(this.config.uplinks[version._verdaccio_uplink].url); + if (uplinkUrl.host === tarballUrl.host) { + tarballUrl.protocol = uplinkUrl.protocol; + hash.registry = version._verdaccio_uplink; + hash.url = URL.format(tarballUrl); } } } } } } - for (let tag in newdata['dist-tags']) { - if (!data['dist-tags'][tag] || data['dist-tags'][tag] !== newdata['dist-tags'][tag]) { + for (let tag in packageInfo['dist-tags']) { + if (!packageLocalJson['dist-tags'][tag] || packageLocalJson['dist-tags'][tag] !== packageInfo['dist-tags'][tag]) { change = true; - data['dist-tags'][tag] = newdata['dist-tags'][tag]; + packageLocalJson['dist-tags'][tag] = packageInfo['dist-tags'][tag]; } } - for (let up in newdata._uplinks) { - if (Object.prototype.hasOwnProperty.call(newdata._uplinks, up)) { - const need_change = !Utils.is_object(data._uplinks[up]) - || newdata._uplinks[up].etag !== data._uplinks[up].etag - || newdata._uplinks[up].fetched !== data._uplinks[up].fetched; + for (let up in packageInfo._uplinks) { + if (Object.prototype.hasOwnProperty.call(packageInfo._uplinks, up)) { + const need_change = !Utils.is_object(packageLocalJson._uplinks[up]) + || packageInfo._uplinks[up].etag !== packageLocalJson._uplinks[up].etag + || packageInfo._uplinks[up].fetched !== packageLocalJson._uplinks[up].fetched; if (need_change) { change = true; - data._uplinks[up] = newdata._uplinks[up]; + packageLocalJson._uplinks[up] = packageInfo._uplinks[up]; } } } - if (newdata.readme !== data.readme) { - data.readme = newdata.readme; + if (packageInfo.readme !== packageLocalJson.readme) { + packageLocalJson.readme = packageInfo.readme; + change = true; + } + + if ('time' in packageInfo) { + packageLocalJson.time = packageInfo.time; change = true; } if (change) { this.logger.debug('updating package info'); - this._writePackage(name, data, function(err) { - callback(err, data); + this._writePackage(name, packageLocalJson, function(err) { + callback(err, packageLocalJson); }); } else { - callback(null, data); + callback(null, packageLocalJson); } }); } @@ -258,6 +264,12 @@ class LocalStorage { + ' != ' + metadata.dist.shasum) ); } } + let currentDate = new Date().toISOString(); + data.time['modified'] = currentDate; + if (('created' in data.time) === false) { + data.time.created = currentDate; + } + data.time[version] = currentDate; data._attachments[tarball].version = version; } } @@ -698,7 +710,7 @@ class LocalStorage { * @param {Object} pkg package reference. */ _normalizePackage(pkg) { - ['versions', 'dist-tags', '_distfiles', '_attachments', '_uplinks'].forEach(function(key) { + ['versions', 'dist-tags', '_distfiles', '_attachments', '_uplinks', 'time'].forEach(function(key) { if (!Utils.is_object(pkg[key])) { pkg[key] = {}; }