UNPKG

@verdaccio/local-storage

Version:

Local storage implementation

319 lines (316 loc) 11.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.resourceNotAvailable = exports.pkgFileName = exports.noSuchFile = exports.fileExist = exports.fSError = exports.default = void 0; var _fs = _interopRequireDefault(require("fs")); var _path = _interopRequireDefault(require("path")); var _debug = _interopRequireDefault(require("debug")); var _lodash = _interopRequireDefault(require("lodash")); var _mkdirp = _interopRequireDefault(require("mkdirp")); var _streams = require("@verdaccio/streams"); var _fileLocking = require("@verdaccio/file-locking"); var _core = require("@verdaccio/core"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /* eslint-disable no-undef */ const fileExist = exports.fileExist = 'EEXISTS'; const noSuchFile = exports.noSuchFile = 'ENOENT'; const resourceNotAvailable = exports.resourceNotAvailable = 'EAGAIN'; const pkgFileName = exports.pkgFileName = 'package.json'; const debug = (0, _debug.default)('verdaccio:plugin:local-storage:fs'); const fSError = function (message, code = 409) { const err = _core.errorUtils.getCode(code, message); // FIXME: we should return http-status codes here instead, future improvement // @ts-ignore err.code = message; return err; }; exports.fSError = fSError; const tempFile = function (str) { return `${str}.tmp${String(Math.random()).substr(2)}`; }; const renameTmp = function (src, dst, _cb) { const cb = err => { if (err) { _fs.default.unlink(src, () => {}); } _cb(err); }; if (process.platform !== 'win32') { return _fs.default.rename(src, dst, cb); } // windows can't remove opened file, // but it seem to be able to rename it const tmp = tempFile(dst); _fs.default.rename(dst, tmp, function (err) { _fs.default.rename(src, dst, cb); if (!err) { _fs.default.unlink(tmp, () => {}); } }); }; class LocalFS { constructor(path, logger) { _defineProperty(this, "path", void 0); _defineProperty(this, "logger", void 0); this.path = path; this.logger = logger; } /** * 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 * @param {*} updateHandler * @param {*} onWrite * @param {*} transformPackage * @param {*} onEnd */ updatePackage(name, updateHandler, onWrite, transformPackage, onEnd) { this._lockAndReadJSON(pkgFileName, (err, json) => { let locked = false; const self = this; // callback that cleans up lock first const unLockCallback = function (lockError) { // eslint-disable-next-line prefer-rest-params const _args = arguments; if (locked) { self._unlockJSON(pkgFileName, () => { // ignore any error from the unlock if (lockError !== null) { debug('lock file: %o has failed with error %o', name, lockError); } onEnd.apply(lockError, _args); }); } else { debug('file: %o has been updated', name); onEnd(..._args); } }; if (!err) { locked = true; debug('file: %o has been locked', name); } if (_lodash.default.isNil(err) === false) { if (err.code === resourceNotAvailable) { return unLockCallback(_core.errorUtils.getInternalError('resource temporarily unavailable')); } else if (err.code === noSuchFile) { return unLockCallback(_core.errorUtils.getNotFound()); } else { return unLockCallback(err); } } updateHandler(json, err => { if (err) { return unLockCallback(err); } onWrite(name, transformPackage(json), unLockCallback); }); }); } deletePackage(packageName, callback) { debug('delete a package %o', packageName); return _fs.default.unlink(this._getStorage(packageName), callback); } removePackage(callback) { debug('remove a package %o', this.path); _fs.default.rmdir(this._getStorage('.'), callback); } createPackage(name, value, cb) { debug('create a package %o', name); this._createFile(this._getStorage(pkgFileName), this._convertToString(value), cb); } savePackage(name, value, cb) { debug('save a package %o', name); this._writeFile(this._getStorage(pkgFileName), this._convertToString(value), cb); } readPackage(name, cb) { debug('read a package %o', name); this._readStorageFile(this._getStorage(pkgFileName)).then(res => { try { const data = JSON.parse(res.toString('utf8')); debug('read storage file %o has succeed', name); cb(null, data); } catch (err) { debug('parse storage file %o has failed with error %o', name, err); cb(err); } }, err => { debug('read storage file %o has failed with error %o', name, err); return cb(err); }); } writeTarball(name) { const uploadStream = new _streams.UploadTarball({}); debug('write a tarball for a package %o', name); let _ended = 0; uploadStream.on('end', function () { _ended = 1; }); const pathName = this._getStorage(name); _fs.default.access(pathName, fileNotFound => { const exists = !fileNotFound; if (exists) { uploadStream.emit('error', fSError(fileExist)); } else { const temporalName = _path.default.join(this.path, `${name}.tmp-${String(Math.random()).replace(/^0\./, '')}`); debug('write a temporal name %o', temporalName); const file = _fs.default.createWriteStream(temporalName); const removeTempFile = () => _fs.default.unlink(temporalName, () => {}); let opened = false; uploadStream.pipe(file); uploadStream.done = function () { const onend = function () { 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 () { removeTempFile(); }); } else { // if the file does not recieve any byte never is opened and has to be removed anyway. removeTempFile(); } file.end(); }; 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); }); } }); return uploadStream; } readTarball(name) { const pathName = this._getStorage(name); debug('read a a tarball %o on path %o', name, pathName); const readTarballStream = new _streams.ReadTarball({}); const readStream = _fs.default.createReadStream(pathName); readStream.on('error', function (err) { debug('error on read a tarball %o with error %o', name, err); readTarballStream.emit('error', err); }); readStream.on('open', function (fd) { _fs.default.fstat(fd, function (err, stats) { if (_lodash.default.isNil(err) === false) { debug('error on read a tarball %o with error %o', name, err); return readTarballStream.emit('error', err); } readTarballStream.emit('content-length', stats.size); readTarballStream.emit('open'); debug('open on read a tarball %o', name); readStream.pipe(readTarballStream); }); }); readTarballStream.abort = function () { debug('abort on read a tarball %o', name); readStream.close(); }; return readTarballStream; } _createFile(name, contents, callback) { debug(' create a new file: %o', name); _fs.default.open(name, 'wx', err => { if (err) { // native EEXIST used here to check exception on fs.open if (err.code === 'EEXIST') { debug('file %o cannot be created, it already exists: %o', name); return callback(fSError(fileExist)); } } this._writeFile(name, contents, callback); }); } _readStorageFile(name) { return new Promise((resolve, reject) => { debug('reading the file: %o', name); _fs.default.readFile(name, (err, data) => { if (err) { debug('error reading the file: %o with error %o', name, err); reject(err); } else { debug('read file %o succeed', name); resolve(data); } }); }); } _convertToString(value) { return JSON.stringify(value, null, '\t'); } _getStorage(fileName = '') { const storagePath = _path.default.join(this.path, fileName); return storagePath; } _writeFile(dest, data, cb) { const createTempFile = cb => { const tempFilePath = tempFile(dest); _fs.default.writeFile(tempFilePath, data, err => { if (err) { debug('error on write the file: %o', dest); return cb(err); } debug('creating a new file:: %o', dest); renameTmp(tempFilePath, dest, cb); }); }; createTempFile(err => { if (err && err.code === noSuchFile) { (0, _mkdirp.default)(_path.default.dirname(dest)).then(() => { createTempFile(cb); }).catch(err => { return cb(err); }); } else { cb(err); } }); } _lockAndReadJSON(name, cb) { const fileName = this._getStorage(name); (0, _fileLocking.readFile)(fileName, { lock: true, parse: true }, (err, res) => { if (err) { debug('error on lock and read json for file: %o', name); return cb(err); } debug('lock and read json for file: %o', name); return cb(null, res); }); } _unlockJSON(name, cb) { (0, _fileLocking.unlockFile)(this._getStorage(name), cb); } } exports.default = LocalFS; //# sourceMappingURL=local-fs.js.map