@verdaccio/local-storage
Advanced tools
Comparing version 0.0.6 to 0.0.7
@@ -13,2 +13,14 @@ 'use strict'; | ||
var _classCallCheck2; | ||
function _load_classCallCheck() { | ||
return _classCallCheck2 = _interopRequireDefault(require('babel-runtime/helpers/classCallCheck')); | ||
} | ||
var _createClass2; | ||
function _load_createClass() { | ||
return _createClass2 = _interopRequireDefault(require('babel-runtime/helpers/createClass')); | ||
} | ||
var _fs; | ||
@@ -38,3 +50,3 @@ | ||
*/ | ||
class LocalData { | ||
var LocalData = function () { | ||
@@ -45,3 +57,5 @@ /** | ||
*/ | ||
constructor(config, logger) { | ||
function LocalData(config, logger) { | ||
(0, (_classCallCheck2 || _load_classCallCheck()).default)(this, LocalData); | ||
this.config = config; | ||
@@ -60,115 +74,138 @@ this.path = this._buildStoragePath(config); | ||
*/ | ||
_buildStoragePath(config) { | ||
// FUTURE: the database might be parameterizable from config.yaml | ||
return (_path || _load_path()).default.join((_path || _load_path()).default.resolve((_path || _load_path()).default.dirname(config.self_path || ''), config.storage, '.sinopia-db.json')); | ||
} | ||
/** | ||
* Fetch local packages. | ||
* @private | ||
* @return {Object} | ||
*/ | ||
_fetchLocalPackages() { | ||
const emptyDatabase = { list: [] }; | ||
try { | ||
const dbFile = (_fs || _load_fs()).default.readFileSync(this.path, 'utf8'); | ||
(0, (_createClass2 || _load_createClass()).default)(LocalData, [{ | ||
key: '_buildStoragePath', | ||
value: function _buildStoragePath(config) { | ||
// FUTURE: the database might be parameterizable from config.yaml | ||
return (_path || _load_path()).default.join((_path || _load_path()).default.resolve((_path || _load_path()).default.dirname(config.self_path || ''), config.storage, '.sinopia-db.json')); | ||
} | ||
if (!dbFile) { | ||
// readFileSync is platform specific, FreeBSD might return null | ||
return emptyDatabase; | ||
} | ||
/** | ||
* Fetch local packages. | ||
* @private | ||
* @return {Object} | ||
*/ | ||
const db = this._parseDatabase(dbFile); | ||
}, { | ||
key: '_fetchLocalPackages', | ||
value: function _fetchLocalPackages() { | ||
var emptyDatabase = { list: [] }; | ||
if (!db) { | ||
try { | ||
var dbFile = (_fs || _load_fs()).default.readFileSync(this.path, 'utf8'); | ||
if (!dbFile) { | ||
// readFileSync is platform specific, FreeBSD might return null | ||
return emptyDatabase; | ||
} | ||
var db = this._parseDatabase(dbFile); | ||
if (!db) { | ||
return emptyDatabase; | ||
} | ||
return db; | ||
} catch (err) { | ||
// readFileSync is platform specific, macOS, Linux and Windows thrown an error | ||
// Only recreate if file not found to prevent data loss | ||
if (err.code !== 'ENOENT') { | ||
this.locked = true; | ||
this.logger.error('Failed to read package database file, please check the error printed below:\n', `File Path: ${this.path}\n\n ${err.message}`); | ||
} | ||
return emptyDatabase; | ||
} | ||
} | ||
return db; | ||
} catch (err) { | ||
// readFileSync is platform specific, macOS, Linux and Windows thrown an error | ||
// Only recreate if file not found to prevent data loss | ||
if (err.code !== 'ENOENT') { | ||
/** | ||
* Parse the local database. | ||
* @param {Object} dbFile | ||
* @private | ||
* @return {Object} | ||
*/ | ||
}, { | ||
key: '_parseDatabase', | ||
value: function _parseDatabase(dbFile) { | ||
try { | ||
return JSON.parse(dbFile); | ||
} catch (err) { | ||
this.logger.error(`Package database file corrupted (invalid JSON), please check the error printed below.\nFile Path: ${this.path}`, err); | ||
this.locked = true; | ||
this.logger.error('Failed to read package database file, please check the error printed below:\n', `File Path: ${this.path}\n\n ${err.message}`); | ||
} | ||
return emptyDatabase; | ||
} | ||
} | ||
/** | ||
* Parse the local database. | ||
* @param {Object} dbFile | ||
* @private | ||
* @return {Object} | ||
*/ | ||
_parseDatabase(dbFile) { | ||
try { | ||
return JSON.parse(dbFile); | ||
} catch (err) { | ||
this.logger.error(`Package database file corrupted (invalid JSON), please check the error printed below.\nFile Path: ${this.path}`, err); | ||
this.locked = true; | ||
/** | ||
* Add a new element. | ||
* @param {*} name | ||
* @return {Error|*} | ||
*/ | ||
}, { | ||
key: 'add', | ||
value: function add(name) { | ||
if (this.data.list.indexOf(name) === -1) { | ||
this.data.list.push(name); | ||
return this.sync(); | ||
} | ||
} | ||
} | ||
/** | ||
* Add a new element. | ||
* @param {*} name | ||
* @return {Error|*} | ||
*/ | ||
add(name) { | ||
if (this.data.list.indexOf(name) === -1) { | ||
this.data.list.push(name); | ||
/** | ||
* Remove an element from the database. | ||
* @param {*} name | ||
* @return {Error|*} | ||
*/ | ||
}, { | ||
key: 'remove', | ||
value: function remove(name) { | ||
var i = this.data.list.indexOf(name); | ||
if (i !== -1) { | ||
this.data.list.splice(i, 1); | ||
} | ||
return this.sync(); | ||
} | ||
} | ||
/** | ||
* Remove an element from the database. | ||
* @param {*} name | ||
* @return {Error|*} | ||
*/ | ||
remove(name) { | ||
const i = this.data.list.indexOf(name); | ||
if (i !== -1) { | ||
this.data.list.splice(i, 1); | ||
/** | ||
* Return all database elements. | ||
* @return {Array} | ||
*/ | ||
}, { | ||
key: 'get', | ||
value: function get() { | ||
return this.data.list; | ||
} | ||
return this.sync(); | ||
} | ||
/** | ||
* Return all database elements. | ||
* @return {Array} | ||
*/ | ||
get() { | ||
return this.data.list; | ||
} | ||
/** | ||
* Syncronize {create} database whether does not exist. | ||
* @return {Error|*} | ||
*/ | ||
/** | ||
* Syncronize {create} database whether does not exist. | ||
* @return {Error|*} | ||
*/ | ||
sync() { | ||
if (this.locked) { | ||
this.logger.error('Database is locked, please check error message printed during startup to prevent data loss.'); | ||
return new Error('Verdaccio database is locked, please contact your administrator to checkout logs during verdaccio startup.'); | ||
} | ||
// Uses sync to prevent ugly race condition | ||
try { | ||
(_mkdirp || _load_mkdirp()).default.sync((_path || _load_path()).default.dirname(this.path)); | ||
} catch (err) { | ||
// perhaps a logger instance? | ||
/* eslint no-empty:off */ | ||
} | ||
}, { | ||
key: 'sync', | ||
value: function sync() { | ||
if (this.locked) { | ||
this.logger.error('Database is locked, please check error message printed during startup to prevent data loss.'); | ||
return new Error('Verdaccio database is locked, please contact your administrator to checkout logs during verdaccio startup.'); | ||
} | ||
// Uses sync to prevent ugly race condition | ||
try { | ||
(_mkdirp || _load_mkdirp()).default.sync((_path || _load_path()).default.dirname(this.path)); | ||
} catch (err) { | ||
// perhaps a logger instance? | ||
/* eslint no-empty:off */ | ||
} | ||
try { | ||
(_fs || _load_fs()).default.writeFileSync(this.path, (0, (_stringify || _load_stringify()).default)(this.data)); | ||
} catch (err) { | ||
return err; | ||
try { | ||
(_fs || _load_fs()).default.writeFileSync(this.path, (0, (_stringify || _load_stringify()).default)(this.data)); | ||
} catch (err) { | ||
return err; | ||
} | ||
} | ||
} | ||
}]); | ||
return LocalData; | ||
}(); | ||
} | ||
exports.default = LocalData; |
@@ -19,2 +19,14 @@ 'use strict'; | ||
var _classCallCheck2; | ||
function _load_classCallCheck() { | ||
return _classCallCheck2 = _interopRequireDefault(require('babel-runtime/helpers/classCallCheck')); | ||
} | ||
var _createClass2; | ||
function _load_createClass() { | ||
return _createClass2 = _interopRequireDefault(require('babel-runtime/helpers/createClass')); | ||
} | ||
var _fs; | ||
@@ -64,17 +76,17 @@ | ||
const fileExist = 'EEXISTS'; | ||
var fileExist = 'EEXISTS'; | ||
const noSuchFile = 'ENOENT'; | ||
var noSuchFile = 'ENOENT'; | ||
const fSError = function fSError(message) { | ||
const err = (0, (_httpErrors || _load_httpErrors()).default)(409, message); | ||
var fSError = function fSError(message) { | ||
var err = (0, (_httpErrors || _load_httpErrors()).default)(409, message); | ||
return err; | ||
}; | ||
const tempFile = function tempFile(str) { | ||
var tempFile = function tempFile(str) { | ||
return `${str}.tmp${String(Math.random()).substr(2)}`; | ||
}; | ||
const renameTmp = function renameTmp(src, dst, _cb) { | ||
const cb = function cb(err) { | ||
var renameTmp = function renameTmp(src, dst, _cb) { | ||
var cb = function cb(err) { | ||
if (err) { | ||
@@ -92,7 +104,7 @@ (_fs || _load_fs()).default.unlink(src, function () {}); | ||
// but it seem to be able to rename it | ||
const tmp = tempFile(dst); | ||
var tmp = tempFile(dst); | ||
(_fs || _load_fs()).default.rename(dst, tmp, function (err) { | ||
(_fs || _load_fs()).default.rename(src, dst, cb); | ||
if (!err) { | ||
(_fs || _load_fs()).default.unlink(tmp, () => {}); | ||
(_fs || _load_fs()).default.unlink(tmp, function () {}); | ||
} | ||
@@ -107,5 +119,6 @@ }); | ||
class LocalFS { | ||
var LocalFS = function () { | ||
function LocalFS(path, logger) { | ||
(0, (_classCallCheck2 || _load_classCallCheck()).default)(this, LocalFS); | ||
constructor(path, logger) { | ||
this.path = path; | ||
@@ -115,215 +128,237 @@ this.logger = logger; | ||
deleteJSON(fileName, callback) { | ||
return (_fs || _load_fs()).default.unlink(this._getStorage(fileName), callback); | ||
} | ||
(0, (_createClass2 || _load_createClass()).default)(LocalFS, [{ | ||
key: 'deleteJSON', | ||
value: function deleteJSON(fileName, callback) { | ||
return (_fs || _load_fs()).default.unlink(this._getStorage(fileName), callback); | ||
} | ||
}, { | ||
key: 'removePackage', | ||
value: function removePackage(dirPath, callback) { | ||
(_fs || _load_fs()).default.rmdir(this._getStorage(dirPath), callback); | ||
} | ||
}, { | ||
key: 'createJSON', | ||
value: function createJSON(name, value, cb) { | ||
this._createFile(this._getStorage(name), (0, (_stringify || _load_stringify()).default)(value, null, '\t'), cb); | ||
} | ||
}, { | ||
key: 'updateJSON', | ||
value: function updateJSON(name, value, cb) { | ||
this._updateFile(this._getStorage(name), (0, (_stringify || _load_stringify()).default)(value, null, '\t'), cb); | ||
} | ||
}, { | ||
key: 'writeJSON', | ||
value: function writeJSON(name, value, cb) { | ||
this._writeFile(this._getStorage(name), (0, (_stringify || _load_stringify()).default)(value, null, '\t'), cb); | ||
} | ||
}, { | ||
key: 'readStorageFile', | ||
value: function readStorageFile(name) { | ||
return new (_promise || _load_promise()).default(function (resolve, reject) { | ||
(_fs || _load_fs()).default.readFile(name, function (err, data) { | ||
if (err) { | ||
reject(err); | ||
} else { | ||
resolve(data); | ||
} | ||
}); | ||
}); | ||
} | ||
}, { | ||
key: 'lock_and_read', | ||
value: function lock_and_read(name, cb) { | ||
(0, (_fileLocking || _load_fileLocking()).readFile)(this._getStorage(name), { lock: true }, function (err, res) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
return cb(null, res); | ||
}); | ||
} | ||
}, { | ||
key: 'lockAndReadJSON', | ||
value: function lockAndReadJSON(name, cb) { | ||
(0, (_fileLocking || _load_fileLocking()).readFile)(this._getStorage(name), { | ||
lock: true, | ||
parse: true | ||
}, function (err, res) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
return cb(null, res); | ||
}); | ||
} | ||
}, { | ||
key: 'unlock_file', | ||
value: function unlock_file(name, cb) { | ||
(0, (_fileLocking || _load_fileLocking()).unlockFile)(this._getStorage(name), cb); | ||
} | ||
}, { | ||
key: '_createFile', | ||
value: function _createFile(name, contents, callback) { | ||
var _this = this; | ||
removePackage(dirPath, callback) { | ||
(_fs || _load_fs()).default.rmdir(this._getStorage(dirPath), callback); | ||
} | ||
(_fs || _load_fs()).default.exists(name, function (exists) { | ||
if (exists) { | ||
return callback(fSError(fileExist)); | ||
} | ||
_this._writeFile(name, contents, callback); | ||
}); | ||
} | ||
}, { | ||
key: '_updateFile', | ||
value: function _updateFile(name, contents, callback) { | ||
var _this2 = this; | ||
createJSON(name, value, cb) { | ||
this._createFile(this._getStorage(name), (0, (_stringify || _load_stringify()).default)(value, null, '\t'), cb); | ||
} | ||
updateJSON(name, value, cb) { | ||
this._updateFile(this._getStorage(name), (0, (_stringify || _load_stringify()).default)(value, null, '\t'), cb); | ||
} | ||
writeJSON(name, value, cb) { | ||
this._writeFile(this._getStorage(name), (0, (_stringify || _load_stringify()).default)(value, null, '\t'), cb); | ||
} | ||
readStorageFile(name) { | ||
return new (_promise || _load_promise()).default((resolve, reject) => { | ||
(_fs || _load_fs()).default.readFile(name, (err, data) => { | ||
if (err) { | ||
reject(err); | ||
} else { | ||
resolve(data); | ||
this.logger.debug('_updateFile:pathName', name); | ||
(_fs || _load_fs()).default.exists(name, function (exists) { | ||
if (!exists) { | ||
return callback(fSError(noSuchFile)); | ||
} | ||
_this2._writeFile(name, contents, callback); | ||
}); | ||
}); | ||
} | ||
} | ||
}, { | ||
key: 'readJSON', | ||
value: function readJSON(name, cb) { | ||
this.readStorageFile(this._getStorage(name)).then(function (res) { | ||
try { | ||
var data = JSON.parse(res.toString('utf8')); | ||
lock_and_read(name, cb) { | ||
(0, (_fileLocking || _load_fileLocking()).readFile)(this._getStorage(name), { lock: true }, function (err, res) { | ||
if (err) { | ||
cb(null, data); | ||
} catch (err) { | ||
cb(err); | ||
} | ||
}, function (err) { | ||
return cb(err); | ||
} | ||
return cb(null, res); | ||
}); | ||
} | ||
}); | ||
} | ||
}, { | ||
key: 'createWriteStream', | ||
value: function createWriteStream(name) { | ||
var uploadStream = new (_streams || _load_streams()).UploadTarball(); | ||
lockAndReadJSON(name, cb) { | ||
(0, (_fileLocking || _load_fileLocking()).readFile)(this._getStorage(name), { | ||
lock: true, | ||
parse: true | ||
}, function (err, res) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
return cb(null, res); | ||
}); | ||
} | ||
var _ended = 0; | ||
uploadStream.on('end', function () { | ||
_ended = 1; | ||
}); | ||
unlock_file(name, cb) { | ||
(0, (_fileLocking || _load_fileLocking()).unlockFile)(this._getStorage(name), cb); | ||
} | ||
var pathName = this._getStorage(name); | ||
_createFile(name, contents, callback) { | ||
(_fs || _load_fs()).default.exists(name, exists => { | ||
if (exists) { | ||
return callback(fSError(fileExist)); | ||
} | ||
this._writeFile(name, contents, callback); | ||
}); | ||
} | ||
(_fs || _load_fs()).default.exists(pathName, function (exists) { | ||
if (exists) { | ||
return uploadStream.emit('error', fSError(fileExist)); | ||
} | ||
_updateFile(name, contents, callback) { | ||
this.logger.debug('_updateFile:pathName', name); | ||
(_fs || _load_fs()).default.exists(name, exists => { | ||
if (!exists) { | ||
return callback(fSError(noSuchFile)); | ||
} | ||
this._writeFile(name, contents, callback); | ||
}); | ||
} | ||
var temporalName = `${name}.tmp-${String(Math.random()).replace(/^0\./, '')}`; | ||
var file = (_fs || _load_fs()).default.createWriteStream(temporalName); | ||
var opened = false; | ||
uploadStream.pipe(file); | ||
readJSON(name, cb) { | ||
this.readStorageFile(this._getStorage(name)).then(function (res) { | ||
try { | ||
const data = JSON.parse(res.toString('utf8')); | ||
cb(null, data); | ||
} catch (err) { | ||
cb(err); | ||
} | ||
}, function (err) { | ||
return cb(err); | ||
}); | ||
} | ||
createWriteStream(name) { | ||
const uploadStream = new (_streams || _load_streams()).UploadTarball(); | ||
let _ended = 0; | ||
uploadStream.on('end', function () { | ||
_ended = 1; | ||
}); | ||
const pathName = this._getStorage(name); | ||
(_fs || _load_fs()).default.exists(pathName, function (exists) { | ||
if (exists) { | ||
return uploadStream.emit('error', fSError(fileExist)); | ||
} | ||
const temporalName = `${name}.tmp-${String(Math.random()).replace(/^0\./, '')}`; | ||
const file = (_fs || _load_fs()).default.createWriteStream(temporalName); | ||
let opened = false; | ||
uploadStream.pipe(file); | ||
uploadStream.done = function () { | ||
const onend = function onend() { | ||
file.on('close', function () { | ||
renameTmp(temporalName, pathName, function (err) { | ||
if (err) { | ||
uploadStream.emit('error', err); | ||
} else { | ||
uploadStream.emit('success'); | ||
} | ||
uploadStream.done = function () { | ||
var onend = function onend() { | ||
file.on('close', function () { | ||
renameTmp(temporalName, pathName, function (err) { | ||
if (err) { | ||
uploadStream.emit('error', err); | ||
} else { | ||
uploadStream.emit('success'); | ||
} | ||
}); | ||
}); | ||
}); | ||
file.end(); | ||
}; | ||
if (_ended) { | ||
onend(); | ||
} else { | ||
uploadStream.on('end', onend); | ||
} | ||
}; | ||
uploadStream.abort = function () { | ||
if (opened) { | ||
opened = false; | ||
file.on('close', function () { | ||
(_fs || _load_fs()).default.unlink(temporalName, function () {}); | ||
}); | ||
} | ||
file.end(); | ||
}; | ||
if (_ended) { | ||
onend(); | ||
} else { | ||
uploadStream.on('end', onend); | ||
} | ||
}; | ||
uploadStream.abort = function () { | ||
if (opened) { | ||
opened = false; | ||
file.on('close', function () { | ||
(_fs || _load_fs()).default.unlink(temporalName, function () {}); | ||
}); | ||
} | ||
file.end(); | ||
}; | ||
file.on('open', function () { | ||
opened = true; | ||
// re-emitting open because it's handled in storage.js | ||
uploadStream.emit('open'); | ||
file.on('open', function () { | ||
opened = true; | ||
// re-emitting open because it's handled in storage.js | ||
uploadStream.emit('open'); | ||
}); | ||
file.on('error', function (err) { | ||
uploadStream.emit('error', err); | ||
}); | ||
}); | ||
file.on('error', function (err) { | ||
uploadStream.emit('error', err); | ||
}); | ||
}); | ||
return uploadStream; | ||
} | ||
return uploadStream; | ||
} | ||
}, { | ||
key: 'createReadStream', | ||
value: function createReadStream(name, readTarballStream) { | ||
var callback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {}; | ||
createReadStream(name, readTarballStream) { | ||
let callback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : () => {}; | ||
var pathName = this._getStorage(name); | ||
const pathName = this._getStorage(name); | ||
let readStream = (_fs || _load_fs()).default.createReadStream(pathName); | ||
readStream.on('error', function (err) { | ||
readTarballStream.emit('error', err); | ||
}); | ||
readStream.on('open', function (fd) { | ||
(_fs || _load_fs()).default.fstat(fd, function (err, stats) { | ||
if ((_lodash || _load_lodash()).default.isNil(err) === false) { | ||
return readTarballStream.emit('error', err); | ||
} | ||
readTarballStream.emit('content-length', stats.size); | ||
readTarballStream.emit('open'); | ||
readStream.pipe(readTarballStream); | ||
var readStream = (_fs || _load_fs()).default.createReadStream(pathName); | ||
readStream.on('error', function (err) { | ||
readTarballStream.emit('error', err); | ||
}); | ||
}); | ||
readStream.on('open', function (fd) { | ||
(_fs || _load_fs()).default.fstat(fd, function (err, stats) { | ||
if ((_lodash || _load_lodash()).default.isNil(err) === false) { | ||
return readTarballStream.emit('error', err); | ||
} | ||
readTarballStream.emit('content-length', stats.size); | ||
readTarballStream.emit('open'); | ||
readStream.pipe(readTarballStream); | ||
}); | ||
}); | ||
readTarballStream = new (_streams || _load_streams()).ReadTarball(); | ||
readTarballStream.abort = function () { | ||
readStream.close(); | ||
}; | ||
return readTarballStream; | ||
} | ||
readTarballStream = new (_streams || _load_streams()).ReadTarball(); | ||
readTarballStream.abort = function () { | ||
readStream.close(); | ||
}; | ||
return readTarballStream; | ||
} | ||
}, { | ||
key: '_getStorage', | ||
value: function _getStorage() { | ||
var name = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; | ||
_getStorage() { | ||
let name = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; | ||
var storagePath = (_path || _load_path()).default.join(this.path, name); | ||
const storagePath = (_path || _load_path()).default.join(this.path, name); | ||
return storagePath; | ||
} | ||
}, { | ||
key: '_writeFile', | ||
value: function _writeFile(dest, data, cb) { | ||
var createTempFile = function createTempFile(cb) { | ||
var tempFilePath = tempFile(dest); | ||
return storagePath; | ||
} | ||
_writeFile(dest, data, cb) { | ||
const createTempFile = cb => { | ||
const tempFilePath = tempFile(dest); | ||
(_fs || _load_fs()).default.writeFile(tempFilePath, data, function (err) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
renameTmp(tempFilePath, dest, cb); | ||
}); | ||
}; | ||
createTempFile(err => { | ||
if (err && err.code === noSuchFile) { | ||
(0, (_mkdirp || _load_mkdirp()).default)((_path || _load_path()).default.dirname(dest), function (err) { | ||
(_fs || _load_fs()).default.writeFile(tempFilePath, data, function (err) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
createTempFile(cb); | ||
renameTmp(tempFilePath, dest, cb); | ||
}); | ||
} else { | ||
cb(err); | ||
} | ||
}); | ||
} | ||
}; | ||
} | ||
createTempFile(function (err) { | ||
if (err && err.code === noSuchFile) { | ||
(0, (_mkdirp || _load_mkdirp()).default)((_path || _load_path()).default.dirname(dest), function (err) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
createTempFile(cb); | ||
}); | ||
} else { | ||
cb(err); | ||
} | ||
}); | ||
} | ||
}]); | ||
return LocalFS; | ||
}(); | ||
exports.default = LocalFS; |
@@ -21,2 +21,14 @@ 'use strict'; | ||
var _classCallCheck2; | ||
function _load_classCallCheck() { | ||
return _classCallCheck2 = _interopRequireDefault(require('babel-runtime/helpers/classCallCheck')); | ||
} | ||
var _createClass2; | ||
function _load_createClass() { | ||
return _createClass2 = _interopRequireDefault(require('babel-runtime/helpers/createClass')); | ||
} | ||
var _assert; | ||
@@ -91,11 +103,11 @@ | ||
// $FlowFixMe | ||
const pkgFileName = 'package.json'; | ||
var pkgFileName = 'package.json'; | ||
/* eslint prefer-rest-params: 0 */ | ||
const fileExist = 'EEXISTS'; | ||
const noSuchFile = 'ENOENT'; | ||
const resourceNotAvailable = 'EAGAIN'; | ||
var fileExist = 'EEXISTS'; | ||
var noSuchFile = 'ENOENT'; | ||
var resourceNotAvailable = 'EAGAIN'; | ||
const generatePackageTemplate = function generatePackageTemplate(name) { | ||
var generatePackageTemplate = function generatePackageTemplate(name) { | ||
return { | ||
@@ -116,5 +128,7 @@ // standard things | ||
*/ | ||
class Storage { | ||
constructor(config, logger, utils) { | ||
var Storage = function () { | ||
function Storage(config, logger, utils) { | ||
(0, (_classCallCheck2 || _load_classCallCheck()).default)(this, Storage); | ||
this.localList = new (_localData || _load_localData()).default(config, logger); | ||
@@ -133,118 +147,135 @@ this.logger = logger.child({ sub: 'fs' }); | ||
*/ | ||
addPackage(name, info, callback) { | ||
const storage = this._getLocalStorage(name); | ||
if (!storage) { | ||
return callback(this.utils.ErrorCode.get404('this package cannot be added')); | ||
} | ||
storage.createJSON(pkgFileName, generatePackageTemplate(name), err => { | ||
if (err && err.code === fileExist) { | ||
return callback(this.utils.ErrorCode.get409()); | ||
} | ||
(0, (_createClass2 || _load_createClass()).default)(Storage, [{ | ||
key: 'addPackage', | ||
value: function addPackage(name, info, callback) { | ||
var _this = this; | ||
const latest = this.utils.getLatestVersion(info); | ||
var storage = this._getLocalStorage(name); | ||
if ((_lodash || _load_lodash()).default.isNil(latest) === false && info.versions[latest]) { | ||
return callback(null, info.versions[latest]); | ||
if (!storage) { | ||
return callback(this.utils.ErrorCode.get404('this package cannot be added')); | ||
} | ||
return callback(); | ||
}); | ||
} | ||
/** | ||
* Remove package. | ||
* @param {*} name | ||
* @param {*} callback | ||
* @return {Function} | ||
*/ | ||
removePackage(name, callback) { | ||
let storage = this._getLocalStorage(name); | ||
if (!storage) { | ||
return callback(this.utils.ErrorCode.get404()); | ||
} | ||
storage.createJSON(pkgFileName, generatePackageTemplate(name), function (err) { | ||
if (err && err.code === fileExist) { | ||
return callback(_this.utils.ErrorCode.get409()); | ||
} | ||
storage.readJSON(pkgFileName, (err, data) => { | ||
if (err) { | ||
if (err.code === noSuchFile) { | ||
return callback(this.utils.ErrorCode.get404()); | ||
} else { | ||
return callback(err); | ||
var latest = _this.utils.getLatestVersion(info); | ||
if ((_lodash || _load_lodash()).default.isNil(latest) === false && info.versions[latest]) { | ||
return callback(null, info.versions[latest]); | ||
} | ||
} | ||
this._normalizePackage(data); | ||
return callback(); | ||
}); | ||
} | ||
let removeFailed = this.localList.remove(name); | ||
/** | ||
* Remove package. | ||
* @param {*} name | ||
* @param {*} callback | ||
* @return {Function} | ||
*/ | ||
if (removeFailed) { | ||
// This will happen when database is locked | ||
return callback(this.utils.ErrorCode.get422(removeFailed.message)); | ||
}, { | ||
key: 'removePackage', | ||
value: function removePackage(name, callback) { | ||
var _this2 = this; | ||
var storage = this._getLocalStorage(name); | ||
if (!storage) { | ||
return callback(this.utils.ErrorCode.get404()); | ||
} | ||
storage.deleteJSON(pkgFileName, function (err) { | ||
storage.readJSON(pkgFileName, function (err, data) { | ||
if (err) { | ||
return callback(err); | ||
if (err.code === noSuchFile) { | ||
return callback(_this2.utils.ErrorCode.get404()); | ||
} else { | ||
return callback(err); | ||
} | ||
} | ||
const attachments = (0, (_keys || _load_keys()).default)(data._attachments); | ||
_this2._normalizePackage(data); | ||
const unlinkNext = function unlinkNext(cb) { | ||
if (attachments.length === 0) { | ||
return cb(); | ||
var removeFailed = _this2.localList.remove(name); | ||
if (removeFailed) { | ||
// This will happen when database is locked | ||
return callback(_this2.utils.ErrorCode.get422(removeFailed.message)); | ||
} | ||
storage.deleteJSON(pkgFileName, function (err) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
var attachments = (0, (_keys || _load_keys()).default)(data._attachments); | ||
const attachment = attachments.shift(); | ||
storage.deleteJSON(attachment, function () { | ||
unlinkNext(cb); | ||
}); | ||
}; | ||
var unlinkNext = function unlinkNext(cb) { | ||
if (attachments.length === 0) { | ||
return cb(); | ||
} | ||
unlinkNext(function () { | ||
// try to unlink the directory, but ignore errors because it can fail | ||
storage.removePackage('.', function (err) { | ||
callback(err); | ||
var attachment = attachments.shift(); | ||
storage.deleteJSON(attachment, function () { | ||
unlinkNext(cb); | ||
}); | ||
}; | ||
unlinkNext(function () { | ||
// try to unlink the directory, but ignore errors because it can fail | ||
storage.removePackage('.', function (err) { | ||
callback(err); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
} | ||
} | ||
/** | ||
* Synchronize remote package info with the local one | ||
* @param {*} name | ||
* @param {*} packageInfo | ||
* @param {*} callback | ||
*/ | ||
updateVersions(name, packageInfo, callback) { | ||
this._readCreatePackage(name, (err, packageLocalJson) => { | ||
if (err) { | ||
return callback(err); | ||
} | ||
/** | ||
* Synchronize remote package info with the local one | ||
* @param {*} name | ||
* @param {*} packageInfo | ||
* @param {*} callback | ||
*/ | ||
let change = false; | ||
for (let versionId in packageInfo.versions) { | ||
if ((_lodash || _load_lodash()).default.isNil(packageLocalJson.versions[versionId])) { | ||
const version = packageInfo.versions[versionId]; | ||
}, { | ||
key: 'updateVersions', | ||
value: function updateVersions(name, packageInfo, callback) { | ||
var _this3 = this; | ||
// we don't keep readmes for package versions, | ||
// only one readme per package | ||
delete version.readme; | ||
this._readCreatePackage(name, function (err, packageLocalJson) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
change = true; | ||
packageLocalJson.versions[versionId] = version; | ||
var change = false; | ||
for (var versionId in packageInfo.versions) { | ||
if ((_lodash || _load_lodash()).default.isNil(packageLocalJson.versions[versionId])) { | ||
var version = packageInfo.versions[versionId]; | ||
if (version.dist && version.dist.tarball) { | ||
const urlObject = (_url || _load_url()).default.parse(version.dist.tarball); | ||
const filename = urlObject.pathname.replace(/^.*\//, ''); | ||
// we don't keep readmes for package versions, | ||
// only one readme per package | ||
delete version.readme; | ||
// we do NOT overwrite any existing records | ||
if ((_lodash || _load_lodash()).default.isNil(packageLocalJson._distfiles[filename])) { | ||
let hash = packageLocalJson._distfiles[filename] = { | ||
url: version.dist.tarball, | ||
sha: version.dist.shasum | ||
}; | ||
// $FlowFixMe | ||
const upLink = version[(0, (_for || _load_for()).default)('__verdaccio_uplink')]; | ||
change = true; | ||
packageLocalJson.versions[versionId] = version; | ||
if ((_lodash || _load_lodash()).default.isNil(upLink) === false) { | ||
this._updateUplinkToRemoteProtocol(hash, upLink); | ||
if (version.dist && version.dist.tarball) { | ||
var urlObject = (_url || _load_url()).default.parse(version.dist.tarball); | ||
var filename = urlObject.pathname.replace(/^.*\//, ''); | ||
// we do NOT overwrite any existing records | ||
if ((_lodash || _load_lodash()).default.isNil(packageLocalJson._distfiles[filename])) { | ||
var hash = packageLocalJson._distfiles[filename] = { | ||
url: version.dist.tarball, | ||
sha: version.dist.shasum | ||
}; | ||
// $FlowFixMe | ||
var upLink = version[(0, (_for || _load_for()).default)('__verdaccio_uplink')]; | ||
if ((_lodash || _load_lodash()).default.isNil(upLink) === false) { | ||
_this3._updateUplinkToRemoteProtocol(hash, upLink); | ||
} | ||
} | ||
@@ -254,722 +285,813 @@ } | ||
} | ||
} | ||
for (let tag in packageInfo['dist-tags']) { | ||
if (!packageLocalJson['dist-tags'][tag] || packageLocalJson['dist-tags'][tag] !== packageInfo['dist-tags'][tag]) { | ||
change = true; | ||
packageLocalJson['dist-tags'][tag] = packageInfo['dist-tags'][tag]; | ||
for (var tag in packageInfo['dist-tags']) { | ||
if (!packageLocalJson['dist-tags'][tag] || packageLocalJson['dist-tags'][tag] !== packageInfo['dist-tags'][tag]) { | ||
change = true; | ||
packageLocalJson['dist-tags'][tag] = packageInfo['dist-tags'][tag]; | ||
} | ||
} | ||
} | ||
for (let up in packageInfo._uplinks) { | ||
if (Object.prototype.hasOwnProperty.call(packageInfo._uplinks, up)) { | ||
const need_change = !this.utils.is_object(packageLocalJson._uplinks[up]) || packageInfo._uplinks[up].etag !== packageLocalJson._uplinks[up].etag || packageInfo._uplinks[up].fetched !== packageLocalJson._uplinks[up].fetched; | ||
for (var up in packageInfo._uplinks) { | ||
if (Object.prototype.hasOwnProperty.call(packageInfo._uplinks, up)) { | ||
var need_change = !_this3.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; | ||
packageLocalJson._uplinks[up] = packageInfo._uplinks[up]; | ||
if (need_change) { | ||
change = true; | ||
packageLocalJson._uplinks[up] = packageInfo._uplinks[up]; | ||
} | ||
} | ||
} | ||
} | ||
if (packageInfo.readme !== packageLocalJson.readme) { | ||
packageLocalJson.readme = packageInfo.readme; | ||
change = true; | ||
} | ||
if (packageInfo.readme !== packageLocalJson.readme) { | ||
packageLocalJson.readme = packageInfo.readme; | ||
change = true; | ||
} | ||
if ('time' in packageInfo) { | ||
packageLocalJson.time = packageInfo.time; | ||
change = true; | ||
} | ||
if ('time' in packageInfo) { | ||
packageLocalJson.time = packageInfo.time; | ||
change = true; | ||
} | ||
if (change) { | ||
this.logger.debug('updating package info'); | ||
this._writePackage(name, packageLocalJson, function (err) { | ||
callback(err, packageLocalJson); | ||
}); | ||
} else { | ||
callback(null, packageLocalJson); | ||
} | ||
}); | ||
} | ||
if (change) { | ||
_this3.logger.debug('updating package info'); | ||
_this3._writePackage(name, packageLocalJson, function (err) { | ||
callback(err, packageLocalJson); | ||
}); | ||
} else { | ||
callback(null, packageLocalJson); | ||
} | ||
}); | ||
} | ||
/** | ||
* Ensure the dist file remains as the same protocol | ||
* @param {Object} hash metadata | ||
* @param {String} upLink registry key | ||
* @private | ||
*/ | ||
_updateUplinkToRemoteProtocol(hash, upLinkKey) { | ||
// if we got this information from a known registry, | ||
// use the same protocol for the tarball | ||
// | ||
const tarballUrl = (_url || _load_url()).default.parse(hash.url); | ||
const uplinkUrl = (_url || _load_url()).default.parse(this.config.uplinks[upLinkKey].url); | ||
/** | ||
* Ensure the dist file remains as the same protocol | ||
* @param {Object} hash metadata | ||
* @param {String} upLink registry key | ||
* @private | ||
*/ | ||
if (uplinkUrl.host === tarballUrl.host) { | ||
tarballUrl.protocol = uplinkUrl.protocol; | ||
hash.registry = upLinkKey; | ||
hash.url = (_url || _load_url()).default.format(tarballUrl); | ||
}, { | ||
key: '_updateUplinkToRemoteProtocol', | ||
value: function _updateUplinkToRemoteProtocol(hash, upLinkKey) { | ||
// if we got this information from a known registry, | ||
// use the same protocol for the tarball | ||
// | ||
var tarballUrl = (_url || _load_url()).default.parse(hash.url); | ||
var uplinkUrl = (_url || _load_url()).default.parse(this.config.uplinks[upLinkKey].url); | ||
if (uplinkUrl.host === tarballUrl.host) { | ||
tarballUrl.protocol = uplinkUrl.protocol; | ||
hash.registry = upLinkKey; | ||
hash.url = (_url || _load_url()).default.format(tarballUrl); | ||
} | ||
} | ||
} | ||
/** | ||
* Add a new version to a previous local package. | ||
* @param {*} name | ||
* @param {*} version | ||
* @param {*} metadata | ||
* @param {*} tag | ||
* @param {*} callback | ||
*/ | ||
addVersion(name, version, metadata, tag, callback) { | ||
this._updatePackage(name, (data, cb) => { | ||
// keep only one readme per package | ||
data.readme = metadata.readme; | ||
/** | ||
* Add a new version to a previous local package. | ||
* @param {*} name | ||
* @param {*} version | ||
* @param {*} metadata | ||
* @param {*} tag | ||
* @param {*} callback | ||
*/ | ||
// TODO: lodash remove | ||
delete metadata.readme; | ||
}, { | ||
key: 'addVersion', | ||
value: function addVersion(name, version, metadata, tag, callback) { | ||
var _this4 = this; | ||
if (data.versions[version] != null) { | ||
return cb(this.utils.ErrorCode.get409()); | ||
} | ||
this._updatePackage(name, function (data, cb) { | ||
// keep only one readme per package | ||
data.readme = metadata.readme; | ||
// if uploaded tarball has a different shasum, it's very likely that we have some kind of error | ||
if (this.utils.is_object(metadata.dist) && (_lodash || _load_lodash()).default.isString(metadata.dist.tarball)) { | ||
let tarball = metadata.dist.tarball.replace(/.*\//, ''); | ||
// TODO: lodash remove | ||
delete metadata.readme; | ||
if (this.utils.is_object(data._attachments[tarball])) { | ||
if (data.versions[version] != null) { | ||
return cb(_this4.utils.ErrorCode.get409()); | ||
} | ||
if ((_lodash || _load_lodash()).default.isNil(data._attachments[tarball].shasum) === false && (_lodash || _load_lodash()).default.isNil(metadata.dist.shasum) === false) { | ||
if (data._attachments[tarball].shasum != metadata.dist.shasum) { | ||
const errorMessage = `shasum error, ${data._attachments[tarball].shasum} != ${metadata.dist.shasum}`; | ||
return cb(this.utils.ErrorCode.get400(errorMessage)); | ||
// if uploaded tarball has a different shasum, it's very likely that we have some kind of error | ||
if (_this4.utils.is_object(metadata.dist) && (_lodash || _load_lodash()).default.isString(metadata.dist.tarball)) { | ||
var tarball = metadata.dist.tarball.replace(/.*\//, ''); | ||
if (_this4.utils.is_object(data._attachments[tarball])) { | ||
if ((_lodash || _load_lodash()).default.isNil(data._attachments[tarball].shasum) === false && (_lodash || _load_lodash()).default.isNil(metadata.dist.shasum) === false) { | ||
if (data._attachments[tarball].shasum != metadata.dist.shasum) { | ||
var errorMessage = `shasum error, ${data._attachments[tarball].shasum} != ${metadata.dist.shasum}`; | ||
return cb(_this4.utils.ErrorCode.get400(errorMessage)); | ||
} | ||
} | ||
} | ||
let currentDate = new Date().toISOString(); | ||
data.time['modified'] = currentDate; | ||
var currentDate = new Date().toISOString(); | ||
data.time['modified'] = currentDate; | ||
if ('created' in data.time === false) { | ||
data.time.created = currentDate; | ||
if ('created' in data.time === false) { | ||
data.time.created = currentDate; | ||
} | ||
data.time[version] = currentDate; | ||
data._attachments[tarball].version = version; | ||
} | ||
} | ||
data.time[version] = currentDate; | ||
data._attachments[tarball].version = version; | ||
data.versions[version] = metadata; | ||
_this4.utils.tag_version(data, version, tag); | ||
var addFailed = _this4.localList.add(name); | ||
if (addFailed) { | ||
return cb(_this4.utils.ErrorCode.get422(addFailed.message)); | ||
} | ||
} | ||
data.versions[version] = metadata; | ||
this.utils.tag_version(data, version, tag); | ||
cb(); | ||
}, callback); | ||
} | ||
let addFailed = this.localList.add(name); | ||
if (addFailed) { | ||
return cb(this.utils.ErrorCode.get422(addFailed.message)); | ||
} | ||
/** | ||
* Merge a new list of tags for a local packages with the existing one. | ||
* @param {*} name | ||
* @param {*} tags | ||
* @param {*} callback | ||
*/ | ||
cb(); | ||
}, callback); | ||
} | ||
}, { | ||
key: 'mergeTags', | ||
value: function mergeTags(name, tags, callback) { | ||
var _this5 = this; | ||
/** | ||
* Merge a new list of tags for a local packages with the existing one. | ||
* @param {*} name | ||
* @param {*} tags | ||
* @param {*} callback | ||
*/ | ||
mergeTags(name, tags, callback) { | ||
this._updatePackage(name, (data, cb) => { | ||
for (let t in tags) { | ||
if (tags[t] === null) { | ||
delete data['dist-tags'][t]; | ||
continue; | ||
this._updatePackage(name, function (data, cb) { | ||
for (var t in tags) { | ||
if (tags[t] === null) { | ||
delete data['dist-tags'][t]; | ||
continue; | ||
} | ||
// be careful here with == (cast) | ||
if ((_lodash || _load_lodash()).default.isNil(data.versions[tags[t]])) { | ||
return cb(_this5._getVersionNotFound()); | ||
} | ||
var key = tags[t]; | ||
_this5.utils.tag_version(data, key, t); | ||
} | ||
// be careful here with == (cast) | ||
if ((_lodash || _load_lodash()).default.isNil(data.versions[tags[t]])) { | ||
return cb(this._getVersionNotFound()); | ||
} | ||
const key = tags[t]; | ||
this.utils.tag_version(data, key, t); | ||
} | ||
cb(); | ||
}, callback); | ||
} | ||
cb(); | ||
}, callback); | ||
} | ||
/** | ||
* Return version not found | ||
* @return {String} | ||
* @private | ||
*/ | ||
_getVersionNotFound() { | ||
return this.utils.ErrorCode.get404('this version doesn\'t exist'); | ||
} | ||
/** | ||
* Return file no available | ||
* @return {String} | ||
* @private | ||
*/ | ||
_getFileNotAvailable() { | ||
return this.utils.ErrorCode.get404('no such file available'); | ||
} | ||
/** | ||
* Return version not found | ||
* @return {String} | ||
* @private | ||
*/ | ||
/** | ||
* Update the package metadata, tags and attachments (tarballs). | ||
* Note: Currently supports unpublishing only. | ||
* @param {*} name | ||
* @param {*} metadata | ||
* @param {*} revision | ||
* @param {*} callback | ||
* @return {Function} | ||
*/ | ||
changePackage(name, metadata, revision, callback) { | ||
if (!this.utils.is_object(metadata.versions) || !this.utils.is_object(metadata['dist-tags'])) { | ||
return callback(this.utils.ErrorCode.get422()); | ||
}, { | ||
key: '_getVersionNotFound', | ||
value: function _getVersionNotFound() { | ||
return this.utils.ErrorCode.get404('this version doesn\'t exist'); | ||
} | ||
/** | ||
* Return file no available | ||
* @return {String} | ||
* @private | ||
*/ | ||
this._updatePackage(name, (data, cb) => { | ||
for (let ver in data.versions) { | ||
if ((_lodash || _load_lodash()).default.isNil(metadata.versions[ver])) { | ||
this.logger.info({ name: name, version: ver }, 'unpublishing @{name}@@{version}'); | ||
delete data.versions[ver]; | ||
for (let file in data._attachments) { | ||
if (data._attachments[file].version === ver) { | ||
delete data._attachments[file].version; | ||
}, { | ||
key: '_getFileNotAvailable', | ||
value: function _getFileNotAvailable() { | ||
return this.utils.ErrorCode.get404('no such file available'); | ||
} | ||
/** | ||
* Update the package metadata, tags and attachments (tarballs). | ||
* Note: Currently supports unpublishing only. | ||
* @param {*} name | ||
* @param {*} metadata | ||
* @param {*} revision | ||
* @param {*} callback | ||
* @return {Function} | ||
*/ | ||
}, { | ||
key: 'changePackage', | ||
value: function changePackage(name, metadata, revision, callback) { | ||
var _this6 = this; | ||
if (!this.utils.is_object(metadata.versions) || !this.utils.is_object(metadata['dist-tags'])) { | ||
return callback(this.utils.ErrorCode.get422()); | ||
} | ||
this._updatePackage(name, function (data, cb) { | ||
for (var ver in data.versions) { | ||
if ((_lodash || _load_lodash()).default.isNil(metadata.versions[ver])) { | ||
_this6.logger.info({ name: name, version: ver }, 'unpublishing @{name}@@{version}'); | ||
delete data.versions[ver]; | ||
for (var file in data._attachments) { | ||
if (data._attachments[file].version === ver) { | ||
delete data._attachments[file].version; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
data['dist-tags'] = metadata['dist-tags']; | ||
cb(); | ||
}, function (err) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
callback(); | ||
}); | ||
} | ||
/** | ||
* Remove a tarball. | ||
* @param {*} name | ||
* @param {*} filename | ||
* @param {*} revision | ||
* @param {*} callback | ||
*/ | ||
removeTarball(name, filename, revision, callback) { | ||
(0, (_assert || _load_assert()).default)(this.utils.validate_name(filename)); | ||
this._updatePackage(name, (data, cb) => { | ||
if (data._attachments[filename]) { | ||
delete data._attachments[filename]; | ||
data['dist-tags'] = metadata['dist-tags']; | ||
cb(); | ||
} else { | ||
cb(this._getFileNotAvailable()); | ||
} | ||
}, err => { | ||
if (err) { | ||
return callback(err); | ||
} | ||
const storage = this._getLocalStorage(name); | ||
}, function (err) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
callback(); | ||
}); | ||
} | ||
if (storage) { | ||
storage.deleteJSON(filename, callback); | ||
} | ||
}); | ||
} | ||
/** | ||
* Remove a tarball. | ||
* @param {*} name | ||
* @param {*} filename | ||
* @param {*} revision | ||
* @param {*} callback | ||
*/ | ||
/** | ||
* Add a tarball. | ||
* @param {String} name | ||
* @param {String} filename | ||
* @return {Stream} | ||
*/ | ||
addTarball(name, filename) { | ||
(0, (_assert || _load_assert()).default)(this.utils.validate_name(filename)); | ||
}, { | ||
key: 'removeTarball', | ||
value: function removeTarball(name, filename, revision, callback) { | ||
var _this7 = this; | ||
let length = 0; | ||
const shaOneHash = (_crypto || _load_crypto()).default.createHash('sha1'); | ||
const uploadStream = new (_streams || _load_streams()).UploadTarball(); | ||
const _transform = uploadStream._transform; | ||
const storage = this._getLocalStorage(name); | ||
uploadStream.abort = function () {}; | ||
uploadStream.done = function () {}; | ||
(0, (_assert || _load_assert()).default)(this.utils.validate_name(filename)); | ||
uploadStream._transform = function (data) { | ||
shaOneHash.update(data); | ||
// measure the length for validation reasons | ||
length += data.length; | ||
_transform.apply(uploadStream, arguments); | ||
}; | ||
this._updatePackage(name, function (data, cb) { | ||
if (data._attachments[filename]) { | ||
delete data._attachments[filename]; | ||
cb(); | ||
} else { | ||
cb(_this7._getFileNotAvailable()); | ||
} | ||
}, function (err) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
var storage = _this7._getLocalStorage(name); | ||
if (name === pkgFileName || name === '__proto__') { | ||
process.nextTick(() => { | ||
uploadStream.emit('error', this.utils.ErrorCode.get403()); | ||
if (storage) { | ||
storage.deleteJSON(filename, callback); | ||
} | ||
}); | ||
return uploadStream; | ||
} | ||
if (!storage) { | ||
process.nextTick(() => { | ||
uploadStream.emit('error', 'can\'t upload this package'); | ||
}); | ||
return uploadStream; | ||
} | ||
/** | ||
* Add a tarball. | ||
* @param {String} name | ||
* @param {String} filename | ||
* @return {Stream} | ||
*/ | ||
const writeStream = storage.createWriteStream(filename); | ||
}, { | ||
key: 'addTarball', | ||
value: function addTarball(name, filename) { | ||
var _this8 = this; | ||
writeStream.on('error', err => { | ||
if (err.code === fileExist) { | ||
uploadStream.emit('error', this.utils.ErrorCode.get409()); | ||
} else if (err.code === noSuchFile) { | ||
// check if package exists to throw an appropriate message | ||
this.getPackageMetadata(name, function (_err, res) { | ||
if (_err) { | ||
uploadStream.emit('error', _err); | ||
} else { | ||
uploadStream.emit('error', err); | ||
} | ||
(0, (_assert || _load_assert()).default)(this.utils.validate_name(filename)); | ||
var length = 0; | ||
var shaOneHash = (_crypto || _load_crypto()).default.createHash('sha1'); | ||
var uploadStream = new (_streams || _load_streams()).UploadTarball(); | ||
var _transform = uploadStream._transform; | ||
var storage = this._getLocalStorage(name); | ||
uploadStream.abort = function () {}; | ||
uploadStream.done = function () {}; | ||
uploadStream._transform = function (data) { | ||
shaOneHash.update(data); | ||
// measure the length for validation reasons | ||
length += data.length; | ||
_transform.apply(uploadStream, arguments); | ||
}; | ||
if (name === pkgFileName || name === '__proto__') { | ||
process.nextTick(function () { | ||
uploadStream.emit('error', _this8.utils.ErrorCode.get403()); | ||
}); | ||
} else { | ||
uploadStream.emit('error', err); | ||
return uploadStream; | ||
} | ||
}); | ||
writeStream.on('open', function () { | ||
// re-emitting open because it's handled in storage.js | ||
uploadStream.emit('open'); | ||
}); | ||
if (!storage) { | ||
process.nextTick(function () { | ||
uploadStream.emit('error', 'can\'t upload this package'); | ||
}); | ||
return uploadStream; | ||
} | ||
writeStream.on('success', () => { | ||
this._updatePackage(name, function updater(data, cb) { | ||
data._attachments[filename] = { | ||
shasum: shaOneHash.digest('hex') | ||
}; | ||
cb(); | ||
}, function (err) { | ||
if (err) { | ||
var writeStream = storage.createWriteStream(filename); | ||
writeStream.on('error', function (err) { | ||
if (err.code === fileExist) { | ||
uploadStream.emit('error', _this8.utils.ErrorCode.get409()); | ||
} else if (err.code === noSuchFile) { | ||
// check if package exists to throw an appropriate message | ||
_this8.getPackageMetadata(name, function (_err, res) { | ||
if (_err) { | ||
uploadStream.emit('error', _err); | ||
} else { | ||
uploadStream.emit('error', err); | ||
} | ||
}); | ||
} else { | ||
uploadStream.emit('error', err); | ||
} else { | ||
uploadStream.emit('success'); | ||
} | ||
}); | ||
}); | ||
uploadStream.abort = function () { | ||
writeStream.abort(); | ||
}; | ||
writeStream.on('open', function () { | ||
// re-emitting open because it's handled in storage.js | ||
uploadStream.emit('open'); | ||
}); | ||
uploadStream.done = function () { | ||
if (!length) { | ||
uploadStream.emit('error', this.utils.ErrorCode.get422('refusing to accept zero-length file')); | ||
writeStream.on('success', function () { | ||
_this8._updatePackage(name, function updater(data, cb) { | ||
data._attachments[filename] = { | ||
shasum: shaOneHash.digest('hex') | ||
}; | ||
cb(); | ||
}, function (err) { | ||
if (err) { | ||
uploadStream.emit('error', err); | ||
} else { | ||
uploadStream.emit('success'); | ||
} | ||
}); | ||
}); | ||
uploadStream.abort = function () { | ||
writeStream.abort(); | ||
} else { | ||
writeStream.done(); | ||
} | ||
}; | ||
}; | ||
uploadStream.pipe(writeStream); | ||
uploadStream.done = function () { | ||
if (!length) { | ||
uploadStream.emit('error', this.utils.ErrorCode.get422('refusing to accept zero-length file')); | ||
writeStream.abort(); | ||
} else { | ||
writeStream.done(); | ||
} | ||
}; | ||
return uploadStream; | ||
} | ||
uploadStream.pipe(writeStream); | ||
/** | ||
* Get a tarball. | ||
* @param {*} name | ||
* @param {*} filename | ||
* @return {ReadTarball} | ||
*/ | ||
getTarball(name, filename) { | ||
(0, (_assert || _load_assert()).default)(this.utils.validate_name(filename)); | ||
const storage = this._getLocalStorage(name); | ||
if ((_lodash || _load_lodash()).default.isNil(storage)) { | ||
return this._createFailureStreamResponse(); | ||
return uploadStream; | ||
} | ||
return this._streamSuccessReadTarBall(storage, filename); | ||
} | ||
/** | ||
* Get a tarball. | ||
* @param {*} name | ||
* @param {*} filename | ||
* @return {ReadTarball} | ||
*/ | ||
/** | ||
* Return a stream that emits a read failure. | ||
* @private | ||
* @return {ReadTarball} | ||
*/ | ||
_createFailureStreamResponse() { | ||
const stream = new (_streams || _load_streams()).ReadTarball(); | ||
}, { | ||
key: 'getTarball', | ||
value: function getTarball(name, filename) { | ||
(0, (_assert || _load_assert()).default)(this.utils.validate_name(filename)); | ||
process.nextTick(() => { | ||
stream.emit('error', this._getFileNotAvailable()); | ||
}); | ||
return stream; | ||
} | ||
var storage = this._getLocalStorage(name); | ||
/** | ||
* Return a stream that emits the tarball data | ||
* @param {Object} storage | ||
* @param {String} filename | ||
* @private | ||
* @return {ReadTarball} | ||
*/ | ||
_streamSuccessReadTarBall(storage, filename) { | ||
const stream = new (_streams || _load_streams()).ReadTarball(); | ||
const readTarballStream = storage.createReadStream(filename); | ||
const e404 = this.utils.ErrorCode.get404; | ||
stream.abort = function () { | ||
if ((_lodash || _load_lodash()).default.isNil(readTarballStream) === false) { | ||
readTarballStream.abort(); | ||
if ((_lodash || _load_lodash()).default.isNil(storage)) { | ||
return this._createFailureStreamResponse(); | ||
} | ||
}; | ||
readTarballStream.on('error', function (err) { | ||
if (err && err.code === noSuchFile) { | ||
stream.emit('error', e404('no such file available')); | ||
} else { | ||
stream.emit('error', err); | ||
} | ||
}); | ||
return this._streamSuccessReadTarBall(storage, filename); | ||
} | ||
readTarballStream.on('content-length', function (v) { | ||
stream.emit('content-length', v); | ||
}); | ||
/** | ||
* Return a stream that emits a read failure. | ||
* @private | ||
* @return {ReadTarball} | ||
*/ | ||
readTarballStream.on('open', function () { | ||
// re-emitting open because it's handled in storage.js | ||
stream.emit('open'); | ||
readTarballStream.pipe(stream); | ||
}); | ||
}, { | ||
key: '_createFailureStreamResponse', | ||
value: function _createFailureStreamResponse() { | ||
var _this9 = this; | ||
return stream; | ||
} | ||
var stream = new (_streams || _load_streams()).ReadTarball(); | ||
/** | ||
* Retrieve a package by name. | ||
* @param {*} name | ||
* @param {*} options | ||
* @param {*} callback | ||
* @return {Function} | ||
*/ | ||
getPackageMetadata(name) { | ||
let callback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : () => {}; | ||
const storage = this._getLocalStorage(name); | ||
if ((_lodash || _load_lodash()).default.isNil(storage)) { | ||
return callback(this.utils.ErrorCode.get404()); | ||
process.nextTick(function () { | ||
stream.emit('error', _this9._getFileNotAvailable()); | ||
}); | ||
return stream; | ||
} | ||
this._readJSON(storage, callback); | ||
} | ||
/** | ||
* Return a stream that emits the tarball data | ||
* @param {Object} storage | ||
* @param {String} filename | ||
* @private | ||
* @return {ReadTarball} | ||
*/ | ||
/** | ||
* Search a local package. | ||
* @param {*} startKey | ||
* @param {*} options | ||
* @return {Function} | ||
*/ | ||
search(startKey, options) { | ||
const stream = new (_stream || _load_stream()).default.PassThrough({ objectMode: true }); | ||
}, { | ||
key: '_streamSuccessReadTarBall', | ||
value: function _streamSuccessReadTarBall(storage, filename) { | ||
var stream = new (_streams || _load_streams()).ReadTarball(); | ||
var readTarballStream = storage.createReadStream(filename); | ||
var e404 = this.utils.ErrorCode.get404; | ||
this._eachPackage((item, cb) => { | ||
(_fs || _load_fs()).default.stat(item.path, (err, stats) => { | ||
if (err) { | ||
return cb(err); | ||
stream.abort = function () { | ||
if ((_lodash || _load_lodash()).default.isNil(readTarballStream) === false) { | ||
readTarballStream.abort(); | ||
} | ||
}; | ||
if (stats.mtime.getTime() > parseInt(startKey, 10)) { | ||
this.getPackageMetadata(item.name, (err, data) => { | ||
if (err) { | ||
return cb(err); | ||
} | ||
const listVersions = (0, (_keys || _load_keys()).default)(data.versions); | ||
const versions = this.utils.semver_sort(listVersions); | ||
const latest = data['dist-tags'] && data['dist-tags'].latest ? data['dist-tags'].latest : versions.pop(); | ||
if (data.versions[latest]) { | ||
const version = data.versions[latest]; | ||
const pkg = { | ||
'name': version.name, | ||
'description': version.description, | ||
'dist-tags': { latest: latest }, | ||
'maintainers': version.maintainers || [version.author].filter(Boolean), | ||
'author': version.author, | ||
'repository': version.repository, | ||
'readmeFilename': version.readmeFilename || '', | ||
'homepage': version.homepage, | ||
'keywords': version.keywords, | ||
'bugs': version.bugs, | ||
'license': version.license, | ||
'time': { | ||
modified: item.time ? new Date(item.time).toISOString() : stats.mtime | ||
}, | ||
'versions': { [latest]: 'latest' } | ||
}; | ||
stream.push(pkg); | ||
} | ||
cb(); | ||
}); | ||
readTarballStream.on('error', function (err) { | ||
if (err && err.code === noSuchFile) { | ||
stream.emit('error', e404('no such file available')); | ||
} else { | ||
cb(); | ||
stream.emit('error', err); | ||
} | ||
}); | ||
}, function on_end(err) { | ||
if (err) { | ||
return stream.emit('error', err); | ||
} | ||
stream.end(); | ||
}); | ||
return stream; | ||
} | ||
readTarballStream.on('content-length', function (v) { | ||
stream.emit('content-length', v); | ||
}); | ||
/** | ||
* Retrieve a wrapper that provide access to the package location. | ||
* @param {Object} packageInfo package name. | ||
* @return {Object} | ||
*/ | ||
_getLocalStorage(packageInfo) { | ||
const path = this.__getLocalStoragePath(this.config.getMatchedPackagesSpec(packageInfo).storage); | ||
readTarballStream.on('open', function () { | ||
// re-emitting open because it's handled in storage.js | ||
stream.emit('open'); | ||
readTarballStream.pipe(stream); | ||
}); | ||
if ((_lodash || _load_lodash()).default.isNil(path) || path === false) { | ||
this.logger.debug({ name: packageInfo }, 'this package has no storage defined: @{name}'); | ||
return null; | ||
return stream; | ||
} | ||
const storagePath = (_path || _load_path()).default.join((_path || _load_path()).default.resolve((_path || _load_path()).default.dirname(this.config.self_path || ''), path), packageInfo); | ||
/** | ||
* Retrieve a package by name. | ||
* @param {*} name | ||
* @param {*} options | ||
* @param {*} callback | ||
* @return {Function} | ||
*/ | ||
return new (_localFs || _load_localFs()).default(storagePath, this.logger); | ||
} | ||
}, { | ||
key: 'getPackageMetadata', | ||
value: function getPackageMetadata(name) { | ||
var callback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {}; | ||
/** | ||
* Read a json file from storage. | ||
* @param {Object} storage | ||
* @param {Function} callback | ||
*/ | ||
_readJSON(storage, callback) { | ||
storage.readJSON(pkgFileName, (err, result) => { | ||
if (err) { | ||
if (err.code === noSuchFile) { | ||
return callback(this.utils.ErrorCode.get404()); | ||
} else { | ||
return callback(this._internalError(err, pkgFileName, 'error reading')); | ||
} | ||
var storage = this._getLocalStorage(name); | ||
if ((_lodash || _load_lodash()).default.isNil(storage)) { | ||
return callback(this.utils.ErrorCode.get404()); | ||
} | ||
this._normalizePackage(result); | ||
callback(err, result); | ||
}); | ||
} | ||
/** | ||
* Verify the right local storage location. | ||
* @param {String} path | ||
* @return {String} | ||
* @private | ||
*/ | ||
__getLocalStoragePath(path) { | ||
if ((_lodash || _load_lodash()).default.isNil(path)) { | ||
path = this.config.storage; | ||
this._readJSON(storage, callback); | ||
} | ||
return path; | ||
} | ||
/** | ||
* Walks through each package and calls `on_package` on them. | ||
* @param {*} onPackage | ||
* @param {*} on_end | ||
*/ | ||
_eachPackage(onPackage, on_end) { | ||
let storages = {}; | ||
let utils = this.utils; | ||
/** | ||
* Search a local package. | ||
* @param {*} startKey | ||
* @param {*} options | ||
* @return {Function} | ||
*/ | ||
storages[this.config.storage] = true; | ||
if (this.config.packages) { | ||
(0, (_keys || _load_keys()).default)(this.config.packages || {}).map(pkg => { | ||
if (this.config.packages[pkg].storage) { | ||
storages[this.config.packages[pkg].storage] = true; | ||
} | ||
}); | ||
} | ||
const base = (_path || _load_path()).default.dirname(this.config.self_path); | ||
}, { | ||
key: 'search', | ||
value: function search(startKey, options) { | ||
var _this10 = this; | ||
(_async || _load_async()).default.eachSeries((0, (_keys || _load_keys()).default)(storages), function (storage, cb) { | ||
(_fs || _load_fs()).default.readdir((_path || _load_path()).default.resolve(base, storage), function (err, files) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
var stream = new (_stream || _load_stream()).default.PassThrough({ objectMode: true }); | ||
(_async || _load_async()).default.eachSeries(files, function (file, cb) { | ||
if (file.match(/^@/)) { | ||
// scoped | ||
(_fs || _load_fs()).default.readdir((_path || _load_path()).default.resolve(base, storage, file), function (err, files) { | ||
this._eachPackage(function (item, cb) { | ||
(_fs || _load_fs()).default.stat(item.path, function (err, stats) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
if (stats.mtime.getTime() > parseInt(startKey, 10)) { | ||
_this10.getPackageMetadata(item.name, function (err, data) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
var listVersions = (0, (_keys || _load_keys()).default)(data.versions); | ||
var versions = _this10.utils.semver_sort(listVersions); | ||
var latest = data['dist-tags'] && data['dist-tags'].latest ? data['dist-tags'].latest : versions.pop(); | ||
(_async || _load_async()).default.eachSeries(files, (file2, cb) => { | ||
if (utils.validate_name(file2)) { | ||
onPackage({ | ||
name: `${file}/${file2}`, | ||
path: (_path || _load_path()).default.resolve(base, storage, file, file2) | ||
}, cb); | ||
} else { | ||
cb(); | ||
} | ||
}, cb); | ||
if (data.versions[latest]) { | ||
var version = data.versions[latest]; | ||
var pkg = { | ||
'name': version.name, | ||
'description': version.description, | ||
'dist-tags': { latest }, | ||
'maintainers': version.maintainers || [version.author].filter(Boolean), | ||
'author': version.author, | ||
'repository': version.repository, | ||
'readmeFilename': version.readmeFilename || '', | ||
'homepage': version.homepage, | ||
'keywords': version.keywords, | ||
'bugs': version.bugs, | ||
'license': version.license, | ||
'time': { | ||
modified: item.time ? new Date(item.time).toISOString() : stats.mtime | ||
}, | ||
'versions': { [latest]: 'latest' } | ||
}; | ||
stream.push(pkg); | ||
} | ||
cb(); | ||
}); | ||
} else if (utils.validate_name(file)) { | ||
onPackage({ | ||
name: file, | ||
path: (_path || _load_path()).default.resolve(base, storage, file) | ||
}, cb); | ||
} else { | ||
cb(); | ||
} | ||
}, cb); | ||
}); | ||
}, function on_end(err) { | ||
if (err) { | ||
return stream.emit('error', err); | ||
} | ||
stream.end(); | ||
}); | ||
}, on_end); | ||
} | ||
/** | ||
* Normalise package properties, tags, revision id. | ||
* @param {Object} pkg package reference. | ||
*/ | ||
_normalizePackage(pkg) { | ||
const pkgProperties = ['versions', 'dist-tags', '_distfiles', '_attachments', '_uplinks', 'time']; | ||
return stream; | ||
} | ||
pkgProperties.forEach(key => { | ||
if (!this.utils.is_object(pkg[key])) { | ||
pkg[key] = {}; | ||
/** | ||
* Retrieve a wrapper that provide access to the package location. | ||
* @param {Object} packageInfo package name. | ||
* @return {Object} | ||
*/ | ||
}, { | ||
key: '_getLocalStorage', | ||
value: function _getLocalStorage(packageInfo) { | ||
var path = this.__getLocalStoragePath(this.config.getMatchedPackagesSpec(packageInfo).storage); | ||
if ((_lodash || _load_lodash()).default.isNil(path) || path === false) { | ||
this.logger.debug({ name: packageInfo }, 'this package has no storage defined: @{name}'); | ||
return null; | ||
} | ||
}); | ||
if ((_lodash || _load_lodash()).default.isString(pkg._rev) === false) { | ||
pkg._rev = '0-0000000000000000'; | ||
var storagePath = (_path || _load_path()).default.join((_path || _load_path()).default.resolve((_path || _load_path()).default.dirname(this.config.self_path || ''), path), packageInfo); | ||
return new (_localFs || _load_localFs()).default(storagePath, this.logger); | ||
} | ||
// normalize dist-tags | ||
this.utils.normalize_dist_tags(pkg); | ||
} | ||
/** | ||
* Retrieve either a previous created local package or a boilerplate. | ||
* @param {*} name | ||
* @param {*} callback | ||
* @return {Function} | ||
*/ | ||
_readCreatePackage(name, callback) { | ||
const storage = this._getLocalStorage(name); | ||
if (!storage) { | ||
const data = generatePackageTemplate(name); | ||
this._normalizePackage(data); | ||
return callback(null, data); | ||
/** | ||
* Read a json file from storage. | ||
* @param {Object} storage | ||
* @param {Function} callback | ||
*/ | ||
}, { | ||
key: '_readJSON', | ||
value: function _readJSON(storage, callback) { | ||
var _this11 = this; | ||
storage.readJSON(pkgFileName, function (err, result) { | ||
if (err) { | ||
if (err.code === noSuchFile) { | ||
return callback(_this11.utils.ErrorCode.get404()); | ||
} else { | ||
return callback(_this11._internalError(err, pkgFileName, 'error reading')); | ||
} | ||
} | ||
_this11._normalizePackage(result); | ||
callback(err, result); | ||
}); | ||
} | ||
storage.readJSON(pkgFileName, (err, data) => { | ||
// TODO: race condition | ||
if (err) { | ||
if (err.code === noSuchFile) { | ||
// if package doesn't exist, we create it here | ||
data = generatePackageTemplate(name); | ||
} else { | ||
return callback(this._internalError(err, pkgFileName, 'error reading')); | ||
} | ||
/** | ||
* Verify the right local storage location. | ||
* @param {String} path | ||
* @return {String} | ||
* @private | ||
*/ | ||
}, { | ||
key: '__getLocalStoragePath', | ||
value: function __getLocalStoragePath(path) { | ||
if ((_lodash || _load_lodash()).default.isNil(path)) { | ||
path = this.config.storage; | ||
} | ||
this._normalizePackage(data); | ||
callback(null, data); | ||
}); | ||
} | ||
return path; | ||
} | ||
/** | ||
* Handle internal error | ||
* @param {*} err | ||
* @param {*} file | ||
* @param {*} message | ||
* @return {Object} Error instance | ||
*/ | ||
_internalError(err, file, message) { | ||
this.logger.error({ err: err, file: file }, message + ' @{file}: @{!err.message}'); | ||
return this.utils.ErrorCode.get500(); | ||
} | ||
/** | ||
* Walks through each package and calls `on_package` on them. | ||
* @param {*} onPackage | ||
* @param {*} on_end | ||
*/ | ||
/** | ||
* This function allows to update the package thread-safely | ||
Algorithm: | ||
1. lock package.json for writing | ||
2. read package.json | ||
3. updateFn(pkg, cb), and wait for cb | ||
4. write package.json.tmp | ||
5. move package.json.tmp package.json | ||
6. callback(err?) | ||
* @param {*} name package name | ||
* @param {*} updateFn function(package, cb) - update function | ||
* @param {*} _callback callback that gets invoked after it's all updated | ||
* @return {Function} | ||
*/ | ||
_updatePackage(name, updateFn, _callback) { | ||
const storage = this._getLocalStorage(name); | ||
if (!storage) { | ||
return _callback(this.utils.ErrorCode.get404()); | ||
}, { | ||
key: '_eachPackage', | ||
value: function _eachPackage(onPackage, on_end) { | ||
var _this12 = this; | ||
var storages = {}; | ||
var utils = this.utils; | ||
storages[this.config.storage] = true; | ||
if (this.config.packages) { | ||
(0, (_keys || _load_keys()).default)(this.config.packages || {}).map(function (pkg) { | ||
if (_this12.config.packages[pkg].storage) { | ||
storages[_this12.config.packages[pkg].storage] = true; | ||
} | ||
}); | ||
} | ||
var base = (_path || _load_path()).default.dirname(this.config.self_path); | ||
(_async || _load_async()).default.eachSeries((0, (_keys || _load_keys()).default)(storages), function (storage, cb) { | ||
(_fs || _load_fs()).default.readdir((_path || _load_path()).default.resolve(base, storage), function (err, files) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
(_async || _load_async()).default.eachSeries(files, function (file, cb) { | ||
if (file.match(/^@/)) { | ||
// scoped | ||
(_fs || _load_fs()).default.readdir((_path || _load_path()).default.resolve(base, storage, file), function (err, files) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
(_async || _load_async()).default.eachSeries(files, function (file2, cb) { | ||
if (utils.validate_name(file2)) { | ||
onPackage({ | ||
name: `${file}/${file2}`, | ||
path: (_path || _load_path()).default.resolve(base, storage, file, file2) | ||
}, cb); | ||
} else { | ||
cb(); | ||
} | ||
}, cb); | ||
}); | ||
} else if (utils.validate_name(file)) { | ||
onPackage({ | ||
name: file, | ||
path: (_path || _load_path()).default.resolve(base, storage, file) | ||
}, cb); | ||
} else { | ||
cb(); | ||
} | ||
}, cb); | ||
}); | ||
}, on_end); | ||
} | ||
storage.lockAndReadJSON(pkgFileName, (err, json) => { | ||
let locked = false; | ||
// callback that cleans up lock first | ||
const callback = function callback(err) { | ||
let _args = arguments; | ||
if (locked) { | ||
storage.unlock_file(pkgFileName, function () { | ||
// ignore any error from the unlock | ||
_callback.apply(err, _args); | ||
}); | ||
} else { | ||
_callback.apply(undefined, (0, (_toConsumableArray2 || _load_toConsumableArray()).default)(_args)); | ||
/** | ||
* Normalise package properties, tags, revision id. | ||
* @param {Object} pkg package reference. | ||
*/ | ||
}, { | ||
key: '_normalizePackage', | ||
value: function _normalizePackage(pkg) { | ||
var _this13 = this; | ||
var pkgProperties = ['versions', 'dist-tags', '_distfiles', '_attachments', '_uplinks', 'time']; | ||
pkgProperties.forEach(function (key) { | ||
if (!_this13.utils.is_object(pkg[key])) { | ||
pkg[key] = {}; | ||
} | ||
}; | ||
}); | ||
if (!err) { | ||
locked = true; | ||
if ((_lodash || _load_lodash()).default.isString(pkg._rev) === false) { | ||
pkg._rev = '0-0000000000000000'; | ||
} | ||
// normalize dist-tags | ||
this.utils.normalize_dist_tags(pkg); | ||
} | ||
if (err) { | ||
if (err.code === resourceNotAvailable) { | ||
return callback(this.utils.ErrorCode.get503()); | ||
} else if (err.code === noSuchFile) { | ||
return callback(this.utils.ErrorCode.get404()); | ||
} else { | ||
return callback(err); | ||
} | ||
/** | ||
* Retrieve either a previous created local package or a boilerplate. | ||
* @param {*} name | ||
* @param {*} callback | ||
* @return {Function} | ||
*/ | ||
}, { | ||
key: '_readCreatePackage', | ||
value: function _readCreatePackage(name, callback) { | ||
var _this14 = this; | ||
var storage = this._getLocalStorage(name); | ||
if (!storage) { | ||
var data = generatePackageTemplate(name); | ||
this._normalizePackage(data); | ||
return callback(null, data); | ||
} | ||
this._normalizePackage(json); | ||
updateFn(json, err => { | ||
storage.readJSON(pkgFileName, function (err, data) { | ||
// TODO: race condition | ||
if (err) { | ||
return callback(err); | ||
if (err.code === noSuchFile) { | ||
// if package doesn't exist, we create it here | ||
data = generatePackageTemplate(name); | ||
} else { | ||
return callback(_this14._internalError(err, pkgFileName, 'error reading')); | ||
} | ||
} | ||
this._writePackage(name, json, callback); | ||
_this14._normalizePackage(data); | ||
callback(null, data); | ||
}); | ||
}); | ||
} | ||
} | ||
/** | ||
* Update the revision (_rev) string for a package. | ||
* @param {*} name | ||
* @param {*} json | ||
* @param {*} callback | ||
* @return {Function} | ||
*/ | ||
_writePackage(name, json, callback) { | ||
// calculate revision a la couchdb | ||
if (typeof json._rev !== 'string') { | ||
json._rev = '0-0000000000000000'; | ||
/** | ||
* Handle internal error | ||
* @param {*} err | ||
* @param {*} file | ||
* @param {*} message | ||
* @return {Object} Error instance | ||
*/ | ||
}, { | ||
key: '_internalError', | ||
value: function _internalError(err, file, message) { | ||
this.logger.error({ err: err, file: file }, message + ' @{file}: @{!err.message}'); | ||
return this.utils.ErrorCode.get500(); | ||
} | ||
const rev = json._rev.split('-'); | ||
json._rev = (+rev[0] || 0) + 1 + '-' + (_crypto || _load_crypto()).default.pseudoRandomBytes(8).toString('hex'); | ||
let storage = this._getLocalStorage(name); | ||
if (!storage) { | ||
return callback(); | ||
/** | ||
* This function allows to update the package thread-safely | ||
Algorithm: | ||
1. lock package.json for writing | ||
2. read package.json | ||
3. updateFn(pkg, cb), and wait for cb | ||
4. write package.json.tmp | ||
5. move package.json.tmp package.json | ||
6. callback(err?) | ||
* @param {*} name package name | ||
* @param {*} updateFn function(package, cb) - update function | ||
* @param {*} _callback callback that gets invoked after it's all updated | ||
* @return {Function} | ||
*/ | ||
}, { | ||
key: '_updatePackage', | ||
value: function _updatePackage(name, updateFn, _callback) { | ||
var _this15 = this; | ||
var storage = this._getLocalStorage(name); | ||
if (!storage) { | ||
return _callback(this.utils.ErrorCode.get404()); | ||
} | ||
storage.lockAndReadJSON(pkgFileName, function (err, json) { | ||
var locked = false; | ||
// callback that cleans up lock first | ||
var callback = function callback(err) { | ||
var _args = arguments; | ||
if (locked) { | ||
storage.unlock_file(pkgFileName, function () { | ||
// ignore any error from the unlock | ||
_callback.apply(err, _args); | ||
}); | ||
} else { | ||
_callback.apply(undefined, (0, (_toConsumableArray2 || _load_toConsumableArray()).default)(_args)); | ||
} | ||
}; | ||
if (!err) { | ||
locked = true; | ||
} | ||
if (err) { | ||
if (err.code === resourceNotAvailable) { | ||
return callback(_this15.utils.ErrorCode.get503()); | ||
} else if (err.code === noSuchFile) { | ||
return callback(_this15.utils.ErrorCode.get404()); | ||
} else { | ||
return callback(err); | ||
} | ||
} | ||
_this15._normalizePackage(json); | ||
updateFn(json, function (err) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
_this15._writePackage(name, json, callback); | ||
}); | ||
}); | ||
} | ||
storage.writeJSON(pkgFileName, json, callback); | ||
} | ||
} | ||
/** | ||
* Update the revision (_rev) string for a package. | ||
* @param {*} name | ||
* @param {*} json | ||
* @param {*} callback | ||
* @return {Function} | ||
*/ | ||
}, { | ||
key: '_writePackage', | ||
value: function _writePackage(name, json, callback) { | ||
// calculate revision a la couchdb | ||
if (typeof json._rev !== 'string') { | ||
json._rev = '0-0000000000000000'; | ||
} | ||
var rev = json._rev.split('-'); | ||
json._rev = (+rev[0] || 0) + 1 + '-' + (_crypto || _load_crypto()).default.pseudoRandomBytes(8).toString('hex'); | ||
var storage = this._getLocalStorage(name); | ||
if (!storage) { | ||
return callback(); | ||
} | ||
storage.writeJSON(pkgFileName, json, callback); | ||
} | ||
}]); | ||
return Storage; | ||
}(); | ||
module.exports = Storage; |
{ | ||
"name": "@verdaccio/local-storage", | ||
"version": "0.0.6", | ||
"version": "0.0.7", | ||
"description": "local storage implementation", | ||
@@ -26,10 +26,8 @@ "main": "lib/index.js", | ||
"babel-eslint": "^7.2.3", | ||
"babel-plugin-array-includes": "2.0.3", | ||
"babel-plugin-istanbul": "4.1.4", | ||
"babel-polyfill": "6.23.0", | ||
"babel-preset-es2015": "6.24.1", | ||
"babel-preset-es2015-node4": "2.1.0", | ||
"babel-preset-flow": "6.23.0", | ||
"babel-plugin-transform-inline-imports-commonjs": "1.0.0", | ||
"babel-plugin-array-includes": "2.0.3", | ||
"babel-plugin-transform-runtime": "6.4.3", | ||
"babel-preset-env": "^1.6.0", | ||
"babel-preset-flow": "6.23.0", | ||
"cross-env": "5.0.5", | ||
@@ -36,0 +34,0 @@ "eslint": "4.4.1", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
49088
17
1388