UNPKG

verdaccio

Version:

A lightweight private npm proxy registry

609 lines (608 loc) 23.2 kB
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } }); const require_runtime = require("../_virtual/_rolldown/runtime.js"); const require_lib_constants = require("./constants.js"); const require_lib_utils = require("./utils.js"); const require_lib_storage_utils = require("./storage-utils.js"); let lodash = require("lodash"); lodash = require_runtime.__toESM(lodash); let _verdaccio_utils = require("@verdaccio/utils"); let debug = require("debug"); debug = require_runtime.__toESM(debug); let assert = require("assert"); assert = require_runtime.__toESM(assert); let _verdaccio_streams = require("@verdaccio/streams"); let _verdaccio_core = require("@verdaccio/core"); //#region src/lib/local-storage.ts var debug$1 = (0, debug.default)("verdaccio:local-storage"); /** * Implements Storage interface (same for storage.js, local-storage.js, up-storage.js). */ var LocalStorage = class { config; storagePlugin; logger; constructor(config, logger, localStorage) { this.logger = logger; this.config = config; this.storagePlugin = localStorage; } addPackage(name, pkg, callback) { const storage = this._getLocalStorage(name); if (lodash.default.isNil(storage)) return callback(require_lib_utils.ErrorCode.getNotFound("this package cannot be added")); storage.createPackage(name, require_lib_storage_utils.generatePackageTemplate(name), (err) => { if (lodash.default.isNull(err) === false && (err.code === require_lib_constants.STORAGE.FILE_EXIST_ERROR || err.code === _verdaccio_core.HTTP_STATUS.CONFLICT)) return callback(require_lib_utils.ErrorCode.getConflict()); const latest = (0, _verdaccio_utils.getLatestVersion)(pkg); if (lodash.default.isNil(latest) === false && pkg.versions[latest]) return callback(null, pkg.versions[latest]); return callback(); }); } /** * Remove package. * @param {*} name * @param {*} callback * @return {Function} */ removePackage(name, callback) { const storage = this._getLocalStorage(name); debug$1("[storage] removing package %o", name); if (lodash.default.isNil(storage)) return callback(require_lib_utils.ErrorCode.getNotFound()); storage.readPackage(name, (err, data) => { if (lodash.default.isNil(err) === false) { if (err.code === require_lib_constants.STORAGE.NO_SUCH_FILE_ERROR || err.code === _verdaccio_core.HTTP_STATUS.NOT_FOUND) return callback(require_lib_utils.ErrorCode.getNotFound()); return callback(err); } data = require_lib_storage_utils.normalizePackage(data); this.storagePlugin.remove(name, (removeFailed) => { if (removeFailed) { this.logger.error({ name }, `[storage/removePackage] the database is locked, removed has failed for @{name}`); return callback(require_lib_utils.ErrorCode.getBadData(removeFailed.message)); } storage.deletePackage(require_lib_constants.STORAGE.PACKAGE_FILE_NAME, (err) => { if (err) return callback(err); const attachments = Object.keys(data._attachments); this._deleteAttachments(storage, attachments, callback); }); }); }); } /** * 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); let change = false; packageLocalJson.readme = require_lib_storage_utils.getLatestReadme(packageInfo); if (packageInfo.readme !== packageLocalJson.readme) change = true; for (const versionId in packageInfo.versions) if (lodash.default.isNil(packageLocalJson.versions[versionId])) { let version = packageInfo.versions[versionId]; version = require_lib_storage_utils.cleanUpReadme(version); version.contributors = (0, _verdaccio_utils.normalizeContributors)(version.contributors); change = true; packageLocalJson.versions[versionId] = version; if (version.dist && version.dist.tarball) { let urlObject; try { urlObject = new URL(version.dist.tarball, "http://localhost"); } catch (err) { this.logger.warn({ name, version: versionId, tarball: version.dist.tarball, err: err.message }, "skipping distfile for @{name}@@{version}: invalid tarball url @{tarball} (@{err})"); continue; } const filename = urlObject.pathname.replace(/^.*\//, ""); if (lodash.default.isNil(packageLocalJson._distfiles[filename])) { const hash = packageLocalJson._distfiles[filename] = { url: version.dist.tarball, sha: version.dist.shasum }; const upLink = version[Symbol.for("__verdaccio_uplink")]; if (lodash.default.isNil(upLink) === false) this._updateUplinkToRemoteProtocol(hash, upLink); } } } for (const tag in packageInfo[require_lib_constants.DIST_TAGS]) if (!packageLocalJson["dist-tags"][tag] || packageLocalJson["dist-tags"][tag] !== packageInfo["dist-tags"][tag]) { change = true; packageLocalJson[require_lib_constants.DIST_TAGS][tag] = packageInfo[require_lib_constants.DIST_TAGS][tag]; } for (const up in packageInfo._uplinks) if (Object.prototype.hasOwnProperty.call(packageInfo._uplinks, up)) { if (!require_lib_utils.isObject(packageLocalJson._uplinks[up]) || packageInfo._uplinks[up].etag !== packageLocalJson._uplinks[up].etag || packageInfo._uplinks[up].fetched !== packageLocalJson._uplinks[up].fetched) { change = true; packageLocalJson._uplinks[up] = packageInfo._uplinks[up]; } } if ("time" in packageInfo && !lodash.default.isEqual(packageLocalJson.time, packageInfo.time)) { packageLocalJson.time = packageInfo.time; change = true; } if (change) { debug$1("updating package %o info", name); this._writePackage(name, packageLocalJson, function(err) { callback(err, packageLocalJson); }); } else callback(null, packageLocalJson); }); } /** * 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) => { data.readme = metadata.readme; metadata = require_lib_storage_utils.cleanUpReadme(metadata); metadata.contributors = (0, _verdaccio_utils.normalizeContributors)(metadata.contributors); if (data.versions[version] != null) return cb(require_lib_utils.ErrorCode.getConflict()); if (require_lib_utils.isObject(metadata.dist) && lodash.default.isString(metadata.dist.tarball)) { const tarball = metadata.dist.tarball.replace(/.*\//, ""); if (require_lib_utils.isObject(data._attachments[tarball])) { if (lodash.default.isNil(data._attachments[tarball].shasum) === false && 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(require_lib_utils.ErrorCode.getBadRequest(errorMessage)); } } const currentDate = (/* @__PURE__ */ new Date()).toISOString(); if (lodash.default.isNil(data.time)) data.time = {}; data.time["modified"] = currentDate; if ("created" in data.time === false) data.time.created = currentDate; data.time[version] = currentDate; data._attachments[tarball].version = version; } } data.versions[version] = metadata; require_lib_utils.tagVersion(data, version, tag); this.storagePlugin.add(name, (addFailed) => { if (addFailed) return cb(require_lib_utils.ErrorCode.getBadData(addFailed.message)); cb(); }); }, callback); } /** * Merge a new list of tags for a local packages with the existing one. * @param {*} pkgName * @param {*} tags * @param {*} callback */ mergeTags(pkgName, tags, callback) { this._updatePackage(pkgName, (data, cb) => { for (const tag in tags) { if (lodash.default.isNull(tags[tag])) { delete data[require_lib_constants.DIST_TAGS][tag]; continue; } if (lodash.default.isNil(data.versions[tags[tag]])) return cb(this._getVersionNotFound()); const version = tags[tag]; require_lib_utils.tagVersion(data, version, tag); } cb(null); }, callback); } /** * Return version not found * @return {String} * @private */ _getVersionNotFound() { return require_lib_utils.ErrorCode.getNotFound(_verdaccio_core.API_ERROR.VERSION_NOT_EXIST); } /** * Return file no available * @return {String} * @private */ _getFileNotAvailable() { return require_lib_utils.ErrorCode.getNotFound("no such file available"); } /** * Update the package metadata, tags and attachments (tarballs). * Note: Currently supports unpublishing and deprecation. * @param {*} name * @param {*} incomingPkg * @param {*} revision * @param {*} callback * @return {Function} */ changePackage(name, incomingPkg, revision, callback) { if (!require_lib_utils.isObject(incomingPkg.versions) || !require_lib_utils.isObject(incomingPkg["dist-tags"])) { this.logger.error({ name }, `changePackage bad data for @{name}`); return callback(require_lib_utils.ErrorCode.getBadData()); } debug$1("changePackage udapting package for %o", name); this._updatePackage(name, (localData, cb) => { for (const version in localData.versions) { const incomingVersion = incomingPkg.versions[version]; if (lodash.default.isNil(incomingVersion)) { this.logger.info({ name, version }, "unpublishing @{name}@@{version}"); delete localData.versions[version]; delete localData.time[version]; for (const file in localData._attachments) if (localData._attachments[file].version === version) delete localData._attachments[file].version; } else if (Object.prototype.hasOwnProperty.call(incomingVersion, "deprecated")) { const incomingDeprecated = incomingVersion.deprecated; if (incomingDeprecated != localData.versions[version].deprecated) { if (!incomingDeprecated) { this.logger.info({ name, version }, "undeprecating @{name}@@{version}"); delete localData.versions[version].deprecated; } else { this.logger.info({ name, version }, "deprecating @{name}@@{version}"); localData.versions[version].deprecated = incomingDeprecated; } localData.time.modified = (/* @__PURE__ */ new Date()).toISOString(); } } } localData[require_lib_constants.USERS] = incomingPkg[require_lib_constants.USERS]; localData[require_lib_constants.DIST_TAGS] = incomingPkg[require_lib_constants.DIST_TAGS]; cb(null); }, 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.default)((0, _verdaccio_utils.validateName)(filename)); this._updatePackage(name, (data, cb) => { if (data._attachments[filename]) { delete data._attachments[filename]; cb(null); } else cb(this._getFileNotAvailable()); }, (err) => { if (err) return callback(err); const storage = this._getLocalStorage(name); if (storage) storage.deletePackage(filename, callback); }); } /** * Add a tarball. * @param {String} name * @param {String} filename * @param {DistFile} distFile distfile record to restore when the manifest * lost it (storages written by other verdaccio versions); persisted * together with the attachment registration once the upload succeeds * @return {Stream} */ addTarball(name, filename, distFile) { (0, assert.default)((0, _verdaccio_utils.validateName)(filename)); let length = 0; const shaOneHash = (0, _verdaccio_utils.createTarballHash)(); const uploadStream = new _verdaccio_streams.UploadTarball({}); const _transform = uploadStream._transform; const storage = this._getLocalStorage(name); uploadStream.abort = function() {}; uploadStream.done = function() {}; uploadStream._transform = function(data, ...args) { shaOneHash.update(data); length += data.length; const appliedData = [data, ...args]; _transform.apply(uploadStream, appliedData); }; if (name === "__proto__") { process.nextTick(() => { uploadStream.emit("error", require_lib_utils.ErrorCode.getForbidden()); }); return uploadStream; } if (!storage) { process.nextTick(() => { uploadStream.emit("error", "can't upload this package"); }); return uploadStream; } const writeStream = storage.writeTarball(filename); writeStream.on("error", (err) => { if (err.code === require_lib_constants.STORAGE.FILE_EXIST_ERROR || err.code === _verdaccio_core.HTTP_STATUS.CONFLICT) { uploadStream.emit("error", require_lib_utils.ErrorCode.getConflict()); uploadStream.abort(); } else if (err.code === require_lib_constants.STORAGE.NO_SUCH_FILE_ERROR || err.code === _verdaccio_core.HTTP_STATUS.NOT_FOUND) this.getPackageMetadata(name, function(_err) { if (_err) uploadStream.emit("error", _err); else uploadStream.emit("error", err); }); else uploadStream.emit("error", err); }); writeStream.on("open", function() { uploadStream.emit("open"); }); writeStream.on("success", () => { this._updatePackage(name, function updater(data, cb) { data._attachments[filename] = { shasum: shaOneHash.digest("hex") }; if (lodash.default.isNil(distFile) === false && lodash.default.isNil(data._distfiles[filename])) { debug$1("restoring missing distfile record for %o served by %o", filename, distFile.registry); data._distfiles[filename] = distFile; } cb(null); }, function(err) { if (err) uploadStream.emit("error", err); else uploadStream.emit("success"); }); }); uploadStream.abort = function() { writeStream.abort(); }; uploadStream.done = function() { if (!length) { uploadStream.emit("error", require_lib_utils.ErrorCode.getBadData("refusing to accept zero-length file")); writeStream.abort(); } else writeStream.done(); }; uploadStream.pipe(writeStream); return uploadStream; } /** * Get a tarball. * @param {*} name * @param {*} filename * @return {ReadTarball} */ getTarball(name, filename) { (0, assert.default)((0, _verdaccio_utils.validateName)(filename)); const storage = this._getLocalStorage(name); if (lodash.default.isNil(storage)) return this._createFailureStreamResponse(); return this._streamSuccessReadTarBall(storage, filename); } /** * Return a stream that emits a read failure. * @private * @return {ReadTarball} */ _createFailureStreamResponse() { const stream = new _verdaccio_streams.ReadTarball({}); process.nextTick(() => { stream.emit("error", this._getFileNotAvailable()); }); return stream; } /** * Return a stream that emits the tarball data * @param {Object} storage * @param {String} filename * @private * @return {ReadTarball} */ _streamSuccessReadTarBall(storage, filename) { const stream = new _verdaccio_streams.ReadTarball({}); const readTarballStream = storage.readTarball(filename); const e404 = require_lib_utils.ErrorCode.getNotFound; stream.abort = function() { if (lodash.default.isNil(readTarballStream) === false) readTarballStream.abort(); }; readTarballStream.on("error", function(err) { if (err.code === require_lib_constants.STORAGE.NO_SUCH_FILE_ERROR || err.code === _verdaccio_core.HTTP_STATUS.NOT_FOUND) stream.emit("error", e404("no such file available")); else stream.emit("error", err); }); readTarballStream.on("content-length", function(content) { stream.emit("content-length", content); }); readTarballStream.on("open", function() { stream.emit("open"); readTarballStream.pipe(stream); }); return stream; } /** * Retrieve a package by name. * @param {*} name * @param {*} callback * @return {Function} */ getPackageMetadata(name, callback = () => {}) { const storage = this._getLocalStorage(name); if (lodash.default.isNil(storage)) return callback(require_lib_utils.ErrorCode.getNotFound()); this._readPackage(name, storage, callback); } getPackageMetadataAsync(name) { return new Promise((resolve, reject) => { this.getPackageMetadata(name, (err, metadata) => { if (err) return reject(err); resolve(metadata); }); }); } updateVersionsAsync(name, packageInfo) { return new Promise((resolve, reject) => { this.updateVersions(name, packageInfo, (err, result) => { if (err) return reject(err); resolve(result); }); }); } /** * Search a local package. * @param {*} startKey * @param {*} options * @return {Function} */ search(startKey, options) { debug$1("options for search %o", options); const stream = new _verdaccio_streams.ReadTarball({ objectMode: true }); this._searchEachPackage((item, cb) => { if (item.time > parseInt(startKey, 10)) this.getPackageMetadata(item.name, (err, data) => { if (err) return cb(err); const result = require_lib_storage_utils.prepareSearchPackage(data, new Date(item.time).toISOString()); if (lodash.default.isNil(result) === false) stream.push(result); cb(null); }); else cb(null); }, function onEnd(err) { if (err) { stream.emit("error", err); return; } stream.end(); }); return stream; } /** * Retrieve a wrapper that provide access to the package location. * @param {Object} pkgName package name. * @return {Object} */ _getLocalStorage(pkgName) { return this.storagePlugin.getPackageStorage(pkgName); } /** * Read a json file from storage. * @param {Object} storage * @param {Function} callback */ _readPackage(name, storage, callback) { storage.readPackage(name, (err, result) => { if (err) { if (err.code === require_lib_constants.STORAGE.NO_SUCH_FILE_ERROR || err.code === _verdaccio_core.HTTP_STATUS.NOT_FOUND) return callback(require_lib_utils.ErrorCode.getNotFound()); return callback(this._internalError(err, require_lib_constants.STORAGE.PACKAGE_FILE_NAME, "error reading")); } callback(err, require_lib_storage_utils.normalizePackage(result)); }); } /** * Walks through each package and calls `on_package` on them. * @param {*} onPackage * @param {*} onEnd */ _searchEachPackage(onPackage, onEnd) { if (lodash.default.isNil(this.storagePlugin.search)) { this.logger.warn("plugin search not implemented yet"); onEnd(); } else this.storagePlugin.search(onPackage, onEnd, _verdaccio_utils.validateName); } /** * Retrieve either a previous created local package or a boilerplate. * @param {*} pkgName * @param {*} callback * @return {Function} */ _readCreatePackage(pkgName, callback) { const storage = this._getLocalStorage(pkgName); if (lodash.default.isNil(storage)) { this._createNewPackage(pkgName, callback); return; } storage.readPackage(pkgName, (err, data) => { if (lodash.default.isNil(err) === false) if (err.code === require_lib_constants.STORAGE.NO_SUCH_FILE_ERROR || err.code === _verdaccio_core.HTTP_STATUS.NOT_FOUND) data = require_lib_storage_utils.generatePackageTemplate(pkgName); else return callback(this._internalError(err, require_lib_constants.STORAGE.PACKAGE_FILE_NAME, "error reading")); callback(null, require_lib_storage_utils.normalizePackage(data)); }); } _createNewPackage(name, callback) { return callback(null, require_lib_storage_utils.normalizePackage(require_lib_storage_utils.generatePackageTemplate(name))); } /** * Handle internal error * @param {*} err * @param {*} file * @param {*} message * @return {Object} Error instance */ _internalError(err, file, message) { this.logger.error({ err, file }, `${message} @{file}: @{!err.message}`); return require_lib_utils.ErrorCode.getInternalError(); } /** * @param {*} name package name * @param {*} updateHandler function(package, cb) - update function * @param {*} callback callback that gets invoked after it's all updated * @return {Function} */ _updatePackage(name, updateHandler, callback) { const storage = this._getLocalStorage(name); if (!storage) return callback(require_lib_utils.ErrorCode.getNotFound()); storage.updatePackage(name, updateHandler, this._writePackage.bind(this), require_lib_storage_utils.normalizePackage, callback); } /** * Update the revision (_rev) string for a package. * @param {*} name * @param {*} json * @param {*} callback * @return {Function} */ _writePackage(name, json, callback) { const storage = this._getLocalStorage(name); if (lodash.default.isNil(storage)) return callback(); storage.savePackage(name, this._setDefaultRevision(json), callback); } _setDefaultRevision(json) { if (lodash.default.isString(json._rev) === false) json._rev = require_lib_constants.STORAGE.DEFAULT_REVISION; if (lodash.default.isNil(this.config._debug)) json._rev = require_lib_storage_utils.generateRevision(json._rev); return json; } _deleteAttachments(storage, attachments, callback) { debug$1("[storage/_deleteAttachments] delete attachments total: %o", attachments?.length); const unlinkNext = function(cb) { if (lodash.default.isEmpty(attachments)) return cb(); const attachment = attachments.shift(); storage.deletePackage(attachment, function() { unlinkNext(cb); }); }; unlinkNext(function() { storage.removePackage(function(err) { callback(err); }); }); } /** * Ensure the dist file remains as the same protocol * @param {Object} hash metadata * @param {String} upLinkKey registry key * @private */ _updateUplinkToRemoteProtocol(hash, upLinkKey) { const tarballUrl = URL.parse(hash.url); const uplinkUrl = URL.parse(this.config.uplinks[upLinkKey].url); if (tarballUrl !== null && uplinkUrl !== null && uplinkUrl.host === tarballUrl.host) { tarballUrl.protocol = uplinkUrl.protocol; hash.registry = upLinkKey; hash.url = tarballUrl.href; } } async getSecret(config) { const secretKey = await this.storagePlugin.getSecret(); return this.storagePlugin.setSecret(config.checkSecretKey(secretKey)); } saveToken(token) { if (lodash.default.isFunction(this.storagePlugin.saveToken) === false) return Promise.reject(require_lib_utils.ErrorCode.getCode(_verdaccio_core.HTTP_STATUS.SERVICE_UNAVAILABLE, _verdaccio_core.SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE)); return this.storagePlugin.saveToken(token); } deleteToken(user, tokenKey) { if (lodash.default.isFunction(this.storagePlugin.deleteToken) === false) return Promise.reject(require_lib_utils.ErrorCode.getCode(_verdaccio_core.HTTP_STATUS.SERVICE_UNAVAILABLE, _verdaccio_core.SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE)); return this.storagePlugin.deleteToken(user, tokenKey); } readTokens(filter) { if (lodash.default.isFunction(this.storagePlugin.readTokens) === false) return Promise.reject(require_lib_utils.ErrorCode.getCode(_verdaccio_core.HTTP_STATUS.SERVICE_UNAVAILABLE, _verdaccio_core.SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE)); return this.storagePlugin.readTokens(filter); } }; //#endregion exports.default = LocalStorage; //# sourceMappingURL=local-storage.js.map